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
79 changes: 63 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ impl CellType for usize {} // for tests
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Iterate over the cells in the range.
/// for (row, col, data) in range.cells() {
Expand Down Expand Up @@ -686,6 +686,40 @@ pub struct Range<T> {
inner: Vec<T>,
}

/// An error for when a [`Range`] cannot be allocated.
///
/// A `Range` is dense, so the extent implied by the cell positions decides
/// the allocation, not the number of cells supplied.
#[derive(Debug)]
pub struct RangeError {
width: u64,
height: u64,
}

impl RangeError {
/// The implied width of the range.
pub fn width(&self) -> u64 {
self.width
}

/// The implied height of the range.
pub fn height(&self) -> u64 {
self.height
}
}

impl std::fmt::Display for RangeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Cannot allocate a {} x {} range",
self.height, self.width
)
}
}

impl std::error::Error for RangeError {}

impl<T: CellType> Range<T> {
/// Creates a new `Range` with default values.
///
Expand Down Expand Up @@ -917,6 +951,11 @@ impl<T: CellType> Range<T> {
///
/// - `cells`: A vector of [`Cell`] elements.
///
/// # Errors
///
/// Returns [`RangeError`] if the dense range implied by the cell positions
/// is too large to allocate.
///
/// # Examples
///
/// An example of creating a new calamine `Range` for a sparse vector of
Expand All @@ -931,17 +970,17 @@ impl<T: CellType> Range<T> {
/// Cell::new((9, 2), Data::Int(1)),
/// ];
///
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// assert_eq!(range.width(), 1);
/// assert_eq!(range.height(), 8);
/// assert_eq!(range.cells().count(), 8);
/// assert_eq!(range.used_cells().count(), 3);
/// ```
///
pub fn from_sparse(cells: Vec<Cell<T>>) -> Range<T> {
pub fn from_sparse(cells: Vec<Cell<T>>) -> Result<Range<T>, RangeError> {
if cells.is_empty() {
return Range::empty();
return Ok(Range::empty());
}
// cells do not always appear in (row, col) order
// search bounds
Expand All @@ -955,10 +994,18 @@ impl<T: CellType> Range<T> {
col_start = min(c, col_start);
col_end = max(c, col_end);
}
let cols = (col_end - col_start + 1) as usize;
let rows = (row_end - row_start + 1) as usize;
let len = cols.saturating_mul(rows);
let mut v = vec![T::default(); len];
// Widened before the `+ 1` so a full `u32` axis cannot overflow.
let width = u64::from(col_end - col_start) + 1;
let height = u64::from(row_end - row_start) + 1;
let size = width.saturating_mul(height);

let mut v = Vec::new();
let len = match usize::try_from(size) {
Ok(len) if v.try_reserve_exact(len).is_ok() => len,
_ => return Err(RangeError { width, height }),
};
v.resize(len, T::default());
let cols = width as usize;
v.shrink_to_fit();
for c in cells {
let row = (c.pos.0 - row_start) as usize;
Expand All @@ -968,11 +1015,11 @@ impl<T: CellType> Range<T> {
*v = c.val;
}
}
Range {
Ok(Range {
start: (row_start, col_start),
end: (row_end, col_end),
inner: v,
}
})
}

/// Set a value at an absolute position in a `Range`.
Expand Down Expand Up @@ -1159,7 +1206,7 @@ impl<T: CellType> Range<T> {
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Iterate over the rows of the range.
/// for (row_num, row) in range.rows().enumerate() {
Expand Down Expand Up @@ -1215,7 +1262,7 @@ impl<T: CellType> Range<T> {
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Iterate over the used cells in the range.
/// for (row, col, data) in range.used_cells() {
Expand Down Expand Up @@ -1259,7 +1306,7 @@ impl<T: CellType> Range<T> {
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Iterate over the cells in the range.
/// for (row, col, data) in range.cells() {
Expand Down Expand Up @@ -1592,7 +1639,7 @@ impl<T: CellType> IndexMut<(usize, usize)> for Range<T> {
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Use the Cells iterator returned by Range::cells().
/// for (row, col, data) in range.cells() {
Expand Down Expand Up @@ -1661,7 +1708,7 @@ impl<'a, T: 'a + CellType> ExactSizeIterator for Cells<'a, T> {}
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Use the UsedCells iterator returned by Range::used_cells().
/// for (row, col, data) in range.used_cells() {
Expand Down Expand Up @@ -1732,7 +1779,7 @@ impl<'a, T: 'a + CellType> DoubleEndedIterator for UsedCells<'a, T> {
/// ];
///
/// // Create a Range from the cells.
/// let range = Range::from_sparse(cells);
/// let range = Range::from_sparse(cells).unwrap();
///
/// // Use the Rows iterator returned by Range::rows().
/// for (row_num, row) in range.rows().enumerate() {
Expand Down
14 changes: 10 additions & 4 deletions src/xls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::utils::read_usize;
use crate::utils::{push_column, read_f64, read_i16, read_i32, read_u16, read_u32};
use crate::vba::VbaProject;
use crate::{
Cell, CellErrorType, Data, Dimensions, HeaderRow, Metadata, Range, Reader, Sheet, SheetType,
SheetVisible,
Cell, CellErrorType, Data, Dimensions, HeaderRow, Metadata, Range, RangeError, Reader, Sheet,
SheetType, SheetVisible,
};

#[derive(Debug)]
Expand Down Expand Up @@ -82,11 +82,15 @@ pub enum XlsError {
/// iFmt value, See 2.4.126 Format
ifmt: u16,
},

/// A worksheet range could not be allocated from its cells.
Range(RangeError),
}

from_err!(std::io::Error, XlsError, Io);
from_err!(crate::cfb::CfbError, XlsError, Cfb);
from_err!(crate::vba::VbaError, XlsError, Vba);
from_err!(RangeError, XlsError, Range);

impl std::fmt::Display for XlsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -120,6 +124,7 @@ impl std::fmt::Display for XlsError {
XlsError::Art(s) => write!(f, "Invalid art record '{s}'"),
XlsError::WorksheetNotFound(name) => write!(f, "Worksheet '{name}' not found"),
XlsError::InvalidFormat { ifmt } => write!(f, "Invalid ifmt value: '{ifmt}'"),
XlsError::Range(e) => write!(f, "{e}"),
}
}
}
Expand All @@ -130,6 +135,7 @@ impl std::error::Error for XlsError {
XlsError::Io(e) => Some(e),
XlsError::Cfb(e) => Some(e),
XlsError::Vba(e) => Some(e),
XlsError::Range(e) => Some(e),
_ => None,
}
}
Expand Down Expand Up @@ -674,8 +680,8 @@ impl<RS: Read + Seek> Xls<RS> {
_ => (),
}
}
let range = Range::from_sparse(cells);
let formula = Range::from_sparse(formulas);
let range = Range::from_sparse(cells)?;
let formula = Range::from_sparse(formulas)?;
sheets.insert(
name,
SheetData {
Expand Down
13 changes: 10 additions & 3 deletions src/xlsb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use crate::utils::{
};
use crate::vba::VbaProject;
use crate::{
Cell, Data, HeaderRow, Metadata, Range, Reader, ReaderRef, Sheet, SheetType, SheetVisible,
Cell, Data, HeaderRow, Metadata, Range, RangeError, Reader, ReaderRef, Sheet, SheetType,
SheetVisible,
};

/// A Xlsb specific error
Expand Down Expand Up @@ -89,6 +90,9 @@ pub enum XlsbError {
WorksheetNotFound(String),
/// XML Encoding error
Encoding(quick_xml::encoding::EncodingError),

/// A worksheet range could not be allocated from its cells.
Range(RangeError),
}

from_err!(std::io::Error, XlsbError, Io);
Expand All @@ -97,6 +101,7 @@ from_err!(quick_xml::Error, XlsbError, Xml);
from_err!(quick_xml::events::attributes::AttrError, XlsbError, XmlAttr);
from_err!(quick_xml::encoding::EncodingError, XlsbError, Encoding);
from_err!(crate::vba::VbaError, XlsbError, Vba);
from_err!(RangeError, XlsbError, Range);

impl std::fmt::Display for XlsbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -127,6 +132,7 @@ impl std::fmt::Display for XlsbError {
XlsbError::Password => write!(f, "Workbook is password protected"),
XlsbError::WorksheetNotFound(name) => write!(f, "Worksheet '{name}' not found"),
XlsbError::Encoding(e) => write!(f, "XML encoding error: {e}"),
XlsbError::Range(e) => write!(f, "{e}"),
}
}
}
Expand All @@ -138,6 +144,7 @@ impl std::error::Error for XlsbError {
XlsbError::Zip(e) => Some(e),
XlsbError::Xml(e) => Some(e),
XlsbError::Vba(e) => Some(e),
XlsbError::Range(e) => Some(e),
_ => None,
}
}
Expand Down Expand Up @@ -536,7 +543,7 @@ impl<RS: Read + Seek> Reader<RS> for Xlsb<RS> {
cells.push(cell);
}
}
Ok(Range::from_sparse(cells))
Ok(Range::from_sparse(cells)?)
}

/// MS-XLSB 2.1.7.62
Expand Down Expand Up @@ -621,7 +628,7 @@ impl<RS: Read + Seek> ReaderRef<RS> for Xlsb<RS> {
}
}

Ok(Range::from_sparse(cells))
Ok(Range::from_sparse(cells)?)
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/xlsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ 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,
Cell, CellErrorType, Data, Dimensions, HeaderRow, Metadata, Range, RangeError, Reader,
ReaderRef, Sheet, SheetType, SheetVisible, Table,
};
pub use cells_reader::{
XlsxCellFormula, XlsxCellFormulaMetadataRecord, XlsxCellReader, XlsxFormulaMetadata,
Expand Down Expand Up @@ -152,6 +152,9 @@ pub enum XlsxError {

/// Specified Pivot Table was not found on worksheet.
PivotTableNotFound(String),

/// A worksheet range could not be allocated from its cells.
Range(RangeError),
}

from_err!(std::io::Error, XlsxError, Io);
Expand All @@ -162,6 +165,7 @@ 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);
from_err!(RangeError, XlsxError, Range);

impl std::fmt::Display for XlsxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -209,6 +213,7 @@ impl std::fmt::Display for XlsxError {
XlsxError::PivotTableNotFound(pt) => {
write!(f, "Pivot Table '{pt}' was not found on worksheet")
}
XlsxError::Range(e) => write!(f, "{e}"),
}
}
}
Expand All @@ -224,6 +229,7 @@ impl std::error::Error for XlsxError {
XlsxError::ParseInt(e) => Some(e),
XlsxError::ParseFloat(e) => Some(e),
XlsxError::Encoding(e) => Some(e),
XlsxError::Range(e) => Some(e),
_ => None,
}
}
Expand Down Expand Up @@ -2617,7 +2623,7 @@ impl<RS: Read + Seek> Reader<RS> for Xlsx<RS> {
cells.push(cell);
}
}
Ok(Range::from_sparse(cells))
Ok(Range::from_sparse(cells)?)
}

fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
Expand Down Expand Up @@ -2717,7 +2723,7 @@ impl<RS: Read + Seek> ReaderRef<RS> for Xlsx<RS> {
}
}

Ok(Range::from_sparse(cells))
Ok(Range::from_sparse(cells)?)
}
}

Expand Down
Binary file added tests/issue_693.xlsx
Binary file not shown.
Loading