// SPDX-License-Identifier: MIT // // Copyright 2016-2026, Johann Tuffe. #![warn(missing_docs)] mod cells_reader; mod style_parser; use std::collections::HashMap; #[cfg(feature = "picture")] use std::collections::HashSet; use std::io::BufReader; use std::io::{Read, Seek}; use std::str::FromStr; use log::warn; use quick_xml::events::{BytesStart, Event}; use quick_xml::name::QName; use quick_xml::Reader as XmlReader; use zip::read::{ZipArchive, ZipFile}; use zip::result::ZipError; use crate::attrs::{decode_attr, local_name_matches, RawAttributes}; use crate::datatype::DataRef; use crate::formats::{ builtin_format_by_id, builtin_format_code_by_id, detect_custom_number_format, CellFormat, }; use crate::style::{Borders, Color, Fill, Font, NumberFormat, Style, StyleRange}; use crate::utils::{ build_zip_path_cache, cached_zip_path, unescape_entity_to_buffer, unescape_xml, }; use crate::vba::VbaProject; #[cfg(feature = "picture")] use crate::Picture; use crate::{ Cell, CellErrorType, Data, Dimensions, HeaderRow, Metadata, Range, Reader, ReaderRef, Sheet, SheetType, SheetVisible, Table, WorkbookProperties, }; pub use cells_reader::{ XlsxCellFormula, XlsxCellFormulaMetadataRecord, XlsxCellReader, XlsxFormulaMetadata, }; pub(crate) type XlReader<'a, RS> = XmlReader>>; /// Maximum number of rows allowed in an XLSX file. pub const MAX_ROWS: u32 = 1_048_576; /// Maximum number of columns allowed in an XLSX file. pub const MAX_COLUMNS: u32 = 16_384; /// An enum for Xlsx specific errors. #[derive(Debug)] pub enum XlsxError { /// A wrapper for a variety of [`std::io::Error`] errors such as file /// permissions when reading an XLSX file. This can be caused by a /// non-existent file or parent directory or, commonly on Windows, if the /// file is already open in Excel. Io(std::io::Error), /// A wrapper for a variety of [`zip::result::ZipError`] errors from /// [`zip::ZipWriter`]. These relate to errors arising from reading the XLSX /// file zip container. Zip(zip::result::ZipError), /// A general error when reading a VBA project from an XLSX file. Vba(crate::vba::VbaError), /// A wrapper for a variety of [`quick_xml::Error`] XML parsing errors, but /// most commonly for missing data in the target file. Xml(quick_xml::Error), /// A wrapper for a variety of [`quick_xml::events::attributes::AttrError`] /// errors related to attributes in XML elements. XmlAttr(quick_xml::events::attributes::AttrError), /// A wrapper for a variety of [`std::string::ParseError`] errors when /// parsing strings. Parse(std::string::ParseError), /// A wrapper for a variety of [`std::num::ParseFloatError`] errors when /// parsing floats. ParseFloat(std::num::ParseFloatError), /// A wrapper for a variety of [`std::num::ParseIntError`] errors when /// parsing integers. ParseInt(std::num::ParseIntError), /// Unexpected end of XML file, usually when an end tag is missing. XmlEof(&'static str), /// Unexpected node in XML. UnexpectedNode(&'static str), /// XML file not found in XLSX container. FileNotFound(String), /// Relationship file not found in XLSX container. RelationshipNotFound, /// Non alphanumeric character found when parsing `A1` style range string. Alphanumeric(u8), /// Error when parsing the column name in a `A1` style range string. NumericColumn(u8), /// Missing column name when parsing an `A1` style range string. RangeWithoutColumnComponent, /// Missing row number when parsing an `A1` style range string. RangeWithoutRowComponent, /// Column number exceeds maximum allowed columns. ColumnNumberOverflow, /// Row number exceeds maximum allowed rows. RowNumberOverflow, /// Error when parsing dimensions of a worksheet. DimensionCount(usize), /// Unknown cell type (`t`) attribute. CellTAttribute(String), /// Unexpected XML element or attribute error. Unexpected(&'static str), /// Unrecognized worksheet type or state. Unrecognized { /// The data type. typ: &'static str, /// The value found. val: String, }, /// Unrecognized cell error type. CellError(String), /// Workbook is password protected. Password, /// Specified worksheet was not found. WorksheetNotFound(String), /// Specified worksheet Table was not found. TableNotFound(String), /// The specified sheet is not a worksheet. NotAWorksheet(String), /// A wrapper for a variety of [`quick_xml::encoding::EncodingError`] /// encoding errors. Encoding(quick_xml::encoding::EncodingError), /// Specified Pivot Table was not found on worksheet. PivotTableNotFound(String), } from_err!(std::io::Error, XlsxError, Io); from_err!(zip::result::ZipError, XlsxError, Zip); from_err!(crate::vba::VbaError, XlsxError, Vba); from_err!(quick_xml::Error, XlsxError, Xml); from_err!(std::num::ParseFloatError, XlsxError, ParseFloat); from_err!(std::num::ParseIntError, XlsxError, ParseInt); from_err!(quick_xml::encoding::EncodingError, XlsxError, Encoding); from_err!(quick_xml::events::attributes::AttrError, XlsxError, XmlAttr); impl std::fmt::Display for XlsxError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { XlsxError::Io(e) => write!(f, "I/O error: {e}"), XlsxError::Zip(e) => write!(f, "Zip error: {e}"), XlsxError::Xml(e) => write!(f, "Xml error: {e}"), XlsxError::XmlAttr(e) => write!(f, "Xml attribute error: {e}"), XlsxError::Vba(e) => write!(f, "Vba error: {e}"), XlsxError::Parse(e) => write!(f, "Parse string error: {e}"), XlsxError::ParseInt(e) => write!(f, "Parse integer error: {e}"), XlsxError::ParseFloat(e) => write!(f, "Parse float error: {e}"), XlsxError::XmlEof(e) => write!(f, "Unexpected end of xml, expecting ''"), XlsxError::UnexpectedNode(e) => write!(f, "Expecting '{e}' node"), XlsxError::FileNotFound(e) => write!(f, "File not found '{e}'"), XlsxError::RelationshipNotFound => write!(f, "Relationship not found"), XlsxError::Alphanumeric(e) => { write!(f, "Expecting alphanumeric character, got {e:X}") } XlsxError::NumericColumn(e) => write!( f, "Numeric character is not allowed for column name, got {e}", ), XlsxError::DimensionCount(e) => { write!(f, "Range dimension must be lower than 2. Got {e}") } XlsxError::CellTAttribute(e) => write!(f, "Unknown cell 't' attribute: {e:?}"), XlsxError::RangeWithoutColumnComponent => { write!(f, "Range is missing the expected column component.") } XlsxError::RangeWithoutRowComponent => { write!(f, "Range is missing the expected row component.") } XlsxError::ColumnNumberOverflow => write!(f, "column number overflow"), XlsxError::RowNumberOverflow => write!(f, "row number overflow"), XlsxError::Unexpected(e) => write!(f, "{e}"), XlsxError::Unrecognized { typ, val } => write!(f, "Unrecognized {typ}: {val}"), XlsxError::CellError(e) => write!(f, "Unsupported cell error value '{e}'"), XlsxError::WorksheetNotFound(n) => write!(f, "Worksheet '{n}' not found"), XlsxError::Password => write!(f, "Workbook is password protected"), XlsxError::TableNotFound(n) => write!(f, "Table '{n}' not found"), XlsxError::NotAWorksheet(typ) => write!(f, "Expecting a worksheet, got {typ}"), XlsxError::Encoding(e) => write!(f, "XML encoding error: {e}"), XlsxError::PivotTableNotFound(pt) => { write!(f, "Pivot Table '{pt}' was not found on worksheet") } } } } impl std::error::Error for XlsxError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { XlsxError::Io(e) => Some(e), XlsxError::Zip(e) => Some(e), XlsxError::Xml(e) => Some(e), XlsxError::Vba(e) => Some(e), XlsxError::Parse(e) => Some(e), XlsxError::ParseInt(e) => Some(e), XlsxError::ParseFloat(e) => Some(e), XlsxError::Encoding(e) => Some(e), _ => None, } } } impl FromStr for CellErrorType { type Err = XlsxError; fn from_str(s: &str) -> Result { match s { "#DIV/0!" => Ok(CellErrorType::Div0), "#N/A" => Ok(CellErrorType::NA), "#NAME?" => Ok(CellErrorType::Name), "#NULL!" => Ok(CellErrorType::Null), "#NUM!" => Ok(CellErrorType::Num), "#REF!" => Ok(CellErrorType::Ref), "#VALUE!" => Ok(CellErrorType::Value), _ => Err(XlsxError::CellError(s.into())), } } } type Tables = Option, Dimensions)>>; /// A struct representing xml zipped excel file /// Xlsx, Xlsm, Xlam pub struct Xlsx { zip: ZipArchive, /// Path to directory containing the main workbook part within zip archive (commonly "xl/") xl_path: String, /// File name of the main workbook part within `xl_path` (commonly "workbook.xml") workbook_name: String, /// Shared strings strings: Vec, /// Sheets paths sheets: Vec<(String, String)>, /// Tables: Name, Sheet, Columns, Data dimensions tables: Tables, /// Cell (number) formats formats: Vec, /// Cell styles, parallel to `formats`, indexed by the cell `s` attribute styles: Vec