sheetsui/src/book/mod.rs

590 lines
20 KiB
Rust
Raw Normal View History

2024-11-16 10:51:38 -05:00
use std::cmp::max;
2024-11-14 18:24:29 -05:00
2024-11-11 19:27:59 -05:00
use anyhow::{anyhow, Result};
2024-11-14 18:24:29 -05:00
use ironcalc::{
base::{
calc_result::Range, expressions::types::Area, types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet}, worksheet::WorksheetDimension, Model, UserModel
2024-11-14 18:24:29 -05:00
},
2024-11-18 18:22:36 -05:00
export::save_xlsx_to_writer,
2024-11-14 18:24:29 -05:00
import::load_from_xlsx,
2024-11-13 19:39:47 -05:00
};
2024-11-11 19:27:59 -05:00
2024-11-16 10:51:38 -05:00
use crate::ui::Address;
#[cfg(test)]
mod test;
const COL_PIXELS: f64 = 5.0;
2024-12-18 20:29:56 -05:00
#[derive(Debug, Clone)]
pub struct AddressRange<'book> {
pub start: &'book Address,
pub end: &'book Address,
}
impl<'book> AddressRange<'book> {
pub fn as_rows(&self) -> Vec<Vec<Address>> {
let (row_range, col_range) = self.get_ranges();
let mut rows = Vec::with_capacity(row_range.len());
for ri in row_range.iter() {
let mut row = Vec::with_capacity(col_range.len());
for ci in col_range.iter() {
row.push(Address { row: *ri, col: *ci });
}
rows.push(row);
}
rows
}
2024-12-18 20:29:56 -05:00
pub fn as_series(&self) -> Vec<Address> {
let (row_range, col_range) = self.get_ranges();
let mut rows = Vec::with_capacity(row_range.len() * col_range.len());
for ri in row_range.iter() {
for ci in col_range.iter() {
rows.push(Address { row: *ri, col: *ci });
}
}
rows
}
fn get_ranges(&self) -> (Vec<usize>, Vec<usize>) {
let row_range = if self.start.row <= self.end.row {
(self.start.row..=self.end.row)
.into_iter()
.collect::<Vec<usize>>()
} else {
let mut v = (self.start.row..=self.end.row)
.into_iter()
.collect::<Vec<usize>>();
v.reverse();
v
};
let col_range = if self.start.col <= self.end.col {
(self.start.col..=self.end.col)
.into_iter()
.collect::<Vec<usize>>()
} else {
let mut v = (self.start.col..=self.end.col)
.into_iter()
.collect::<Vec<usize>>();
v.reverse();
v
};
(row_range, col_range)
}
}
2024-11-13 19:02:41 -05:00
/// A spreadsheet book with some internal state tracking.
2024-11-11 19:27:59 -05:00
pub struct Book {
pub(crate) model: UserModel,
2024-11-16 10:51:38 -05:00
pub current_sheet: u32,
pub location: crate::ui::Address,
2024-11-11 19:27:59 -05:00
}
impl Book {
2024-11-14 18:24:29 -05:00
/// Construct a new book from a Model
pub fn new(model: UserModel) -> Self {
2024-11-14 18:24:29 -05:00
Self {
model,
current_sheet: 0,
2024-11-16 10:51:38 -05:00
location: Address::default(),
2024-11-14 18:24:29 -05:00
}
}
pub fn from_model(model: Model) -> Self {
Self::new(UserModel::from_model(model))
}
/// Construct a new book from an xlsx file.
2024-11-16 10:51:38 -05:00
pub fn new_from_xlsx(path: &str) -> Result<Self> {
Ok(Self::from_model(load_from_xlsx(
path,
"en",
"America/New_York",
)?))
2024-11-16 10:51:38 -05:00
}
/// Evaluate the spreadsheet calculating formulas and style changes.
/// This can be an expensive operation.
2024-11-16 10:51:38 -05:00
pub fn evaluate(&mut self) {
self.model.evaluate();
}
2024-11-14 18:24:29 -05:00
// TODO(zaphar): Should I support ICalc?
/// Construct a new book from a path.
2024-11-16 10:51:38 -05:00
pub fn new_from_xlsx_with_locale(path: &str, locale: &str, tz: &str) -> Result<Self> {
Ok(Self::from_model(load_from_xlsx(path, locale, tz)?))
2024-11-14 18:24:29 -05:00
}
/// Save book to an xlsx file.
pub fn save_to_xlsx(&self, path: &str) -> Result<()> {
2024-11-18 18:22:36 -05:00
// TODO(zaphar): Currently overwrites. Should we prompty in this case?
let file_path = std::path::Path::new(path);
let file = std::fs::File::create(file_path)?;
let writer = std::io::BufWriter::new(file);
save_xlsx_to_writer(self.model.get_model(), writer)?;
2024-11-14 18:24:29 -05:00
Ok(())
}
/// Get all the sheet identiers a `Vec<(String, u32)>` where the string
/// is the sheet name and the u32 is the sheet index.
2024-11-16 10:51:38 -05:00
pub fn get_all_sheets_identifiers(&self) -> Vec<(String, u32)> {
self.model
.get_worksheets_properties()
2024-11-16 10:51:38 -05:00
.iter()
.map(|sheet| (sheet.name.to_owned(), sheet.sheet_id))
2024-11-16 10:51:38 -05:00
.collect()
}
/// Get the current sheets name.
2024-11-11 19:27:59 -05:00
pub fn get_sheet_name(&self) -> Result<&str> {
Ok(&self.get_sheet()?.name)
}
pub fn set_sheet_name(&mut self, idx: u32, sheet_name: &str) -> Result<()> {
self.model.rename_sheet(idx, sheet_name).map_err(|e| anyhow!(e))?;
2024-11-29 09:29:28 -05:00
Ok(())
}
2024-11-29 09:44:53 -05:00
pub fn new_sheet(&mut self, sheet_name: Option<&str>) -> Result<()> {
self.model.new_sheet().map_err(|e| anyhow!(e))?;
let idx = self.model.get_selected_sheet();
if let Some(name) = sheet_name {
self.set_sheet_name(idx, name)?;
}
self.model.set_selected_sheet(self.current_sheet).map_err(|e| anyhow!(e))?;
Ok(())
2024-11-29 09:44:53 -05:00
}
2024-11-13 19:02:41 -05:00
/// Get the sheet data for the current worksheet.
2024-11-11 19:27:59 -05:00
pub fn get_sheet_data(&self) -> Result<&SheetData> {
Ok(&self.get_sheet()?.sheet_data)
}
2024-11-13 19:02:41 -05:00
/// Move to a specific sheet location in the current sheet
pub fn move_to(&mut self, Address { row, col }: &Address) -> Result<()> {
2024-11-16 10:51:38 -05:00
// FIXME(zaphar): Check that this is safe first.
self.location.row = *row;
self.location.col = *col;
2024-11-16 10:51:38 -05:00
Ok(())
}
2024-12-18 20:29:56 -05:00
/// Extend a cell to the rest of the range.
pub fn extend_to(&mut self, from: &Address, to: &Address) -> Result<()> {
2024-12-18 20:29:56 -05:00
for cell in (AddressRange {
start: from,
end: to,
})
.as_series()
.iter()
.skip(1)
{
// TODO(jeremy): Is there a better way to do this using UserModel?
2024-12-18 20:29:56 -05:00
let contents = self
.model
.get_model()
2024-12-18 20:29:56 -05:00
.extend_to(
self.current_sheet,
from.row as i32,
from.col as i32,
cell.row as i32,
cell.col as i32,
)
.map_err(|e| anyhow!(e))?;
self.model
.set_user_input(
self.current_sheet,
cell.row as i32,
cell.col as i32,
&contents,
2024-12-18 20:29:56 -05:00
)
.map_err(|e| anyhow!(e))?;
}
self.evaluate();
Ok(())
}
2024-11-16 10:51:38 -05:00
2024-12-06 17:23:04 -05:00
pub fn clear_current_cell(&mut self) -> Result<()> {
self.clear_cell_contents(self.current_sheet as u32, self.location.clone())
}
2024-12-18 20:29:56 -05:00
2024-12-06 17:23:04 -05:00
pub fn clear_current_cell_all(&mut self) -> Result<()> {
self.clear_cell_all(self.current_sheet as u32, self.location.clone())
}
2024-12-18 20:29:56 -05:00
pub fn clear_cell_contents(&mut self, sheet: u32, Address { row, col }: Address) -> Result<()> {
2024-12-06 17:23:04 -05:00
Ok(self
.model
.range_clear_contents(&Area {
sheet,
row: row as i32,
column: col as i32,
width: 1,
height: 1,
})
2024-12-06 17:23:04 -05:00
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?)
}
pub fn clear_cell_range(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> {
let area = Area {
sheet,
row: start.row as i32,
column: start.col as i32,
width: (end.row - start.row) as i32,
height: (end.col - end.row) as i32,
};
self.model
.range_clear_contents(&area)
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?;
2024-12-06 17:23:04 -05:00
Ok(())
}
2024-12-18 20:29:56 -05:00
pub fn clear_cell_all(&mut self, sheet: u32, Address { row, col }: Address) -> Result<()> {
2024-12-06 17:23:04 -05:00
Ok(self
.model
.range_clear_all(&Area {
sheet,
row: row as i32,
column: col as i32,
width: 1,
height: 1,
})
2024-12-06 17:23:04 -05:00
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?)
}
pub fn clear_cell_range_all(&mut self, sheet: u32, start: Address, end: Address) -> Result<()> {
let area = Area {
sheet,
row: start.row as i32,
column: start.col as i32,
width: (end.row - start.row) as i32,
height: (end.col - end.row) as i32,
};
self.model.range_clear_all(&area)
.map_err(|s| anyhow!("Unable to clear cell contents {}", s))?;
2024-12-06 17:23:04 -05:00
Ok(())
}
2024-11-13 19:02:41 -05:00
/// Get a cells formatted content.
2024-11-16 10:51:38 -05:00
pub fn get_current_cell_rendered(&self) -> Result<String> {
Ok(self.get_cell_addr_rendered(&self.location)?)
2024-11-16 10:51:38 -05:00
}
pub fn get_cell_style(&self, sheet: u32, cell: &Address) -> Option<Style> {
// TODO(jwall): This is modeled a little weird. We should probably record
// the error *somewhere* but for the user there is nothing to be done except
// not use a style.
match self.model.get_model().get_style_for_cell(sheet, cell.row as i32, cell.col as i32)
{
Err(_) => None,
Ok(s) => Some(s),
}
}
fn get_column(&self, sheet: u32, col: usize) -> Result<Option<&Col>> {
Ok(self.model.get_model().workbook.worksheet(sheet)
.map_err(|e| anyhow!("{}", e))?.cols.get(col))
}
fn get_row(&self, sheet: u32, col: usize) -> Result<Option<&Row>> {
Ok(self.model.get_model().workbook.worksheet(sheet)
.map_err(|e| anyhow!("{}", e))?.rows.get(col))
}
pub fn get_column_style(&self, sheet: u32, col: usize) -> Result<Option<Style>> {
// TODO(jwall): This is modeled a little weird. We should probably record
// the error *somewhere* but for the user there is nothing to be done except
// not use a style.
if let Some(col) = self.get_column(sheet, col)? {
if let Some(style_idx) = col.style.map(|idx| idx as usize) {
let styles = &self.model.get_model().workbook.styles;
if styles.cell_style_xfs.len() <= style_idx {
return Ok(Some(Style {
alignment: None,
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
fill: styles.fills[style_idx].clone(),
font: styles.fonts[style_idx].clone(),
border: styles.borders[style_idx].clone(),
quote_prefix: false,
}));
}
}
}
return Ok(None);
}
pub fn get_row_style(&self, sheet: u32, row: usize) -> Result<Option<Style>> {
// TODO(jwall): This is modeled a little weird. We should probably record
// the error *somewhere* but for the user there is nothing to be done except
// not use a style.
if let Some(row) = self.get_row(sheet, row)? {
let style_idx = row.s as usize;
let styles = &self.model.get_model().workbook.styles;
if styles.cell_style_xfs.len() <= style_idx {
return Ok(Some(Style {
alignment: None,
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
fill: styles.fills[style_idx].clone(),
font: styles.fonts[style_idx].clone(),
border: styles.borders[style_idx].clone(),
quote_prefix: false,
}));
}
}
return Ok(None);
}
pub fn create_style(&mut self) -> Style {
Style {
alignment: None,
num_fmt: String::new(),
fill: Fill::default(),
font: Font::default(),
border: Border::default(),
quote_prefix: false,
}
}
pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> {
for (path, val) in style {
self.model.update_range_style(area, path, val)
.map_err(|s| anyhow!("Unable to format cell {}", s))?;
}
Ok(())
}
pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> {
todo!()
//let idx = self.create_or_get_style_idx(style);
//let sheet = self.model.workbook.worksheet_mut(sheet)
// .map_err(|e| anyhow!("{}", e))?;
//let width = sheet.get_column_width(col as i32)
// .map_err(|e| anyhow!("{}", e))?;
//sheet.set_column_style(col as i32, idx)
// .map_err(|e| anyhow!("{}", e))?;
//sheet.set_column_width(col as i32, width)
// .map_err(|e| anyhow!("{}", e))?;
//Ok(())
}
pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> {
todo!()
//let idx = self.create_or_get_style_idx(style);
//self.model.workbook.worksheet_mut(sheet)
// .map_err(|e| anyhow!("{}", e))?
// .set_row_style(row as i32, idx)
// .map_err(|e| anyhow!("{}", e))?;
//Ok(())
}
/// Get a cells rendered content for display.
pub fn get_cell_addr_rendered(&self, Address { row, col }: &Address) -> Result<String> {
2024-11-13 19:39:47 -05:00
Ok(self
.model
.get_formatted_cell_value(self.current_sheet, *row as i32, *col as i32)
2024-11-13 19:02:41 -05:00
.map_err(|s| anyhow!("Unable to format cell {}", s))?)
}
2024-12-18 20:29:56 -05:00
2024-12-08 20:34:08 -05:00
/// Get a cells actual content unformatted as a string.
pub fn get_cell_addr_contents(&self, Address { row, col }: &Address) -> Result<String> {
Ok(self
.model
.get_cell_content(self.current_sheet, *row as i32, *col as i32)
.map_err(|s| anyhow!("Unable to format cell {}", s))?)
}
2024-11-13 19:02:41 -05:00
/// Get a cells actual content as a string.
2024-11-16 10:51:38 -05:00
pub fn get_current_cell_contents(&self) -> Result<String> {
2024-11-13 19:39:47 -05:00
Ok(self
.model
.get_cell_content(
self.current_sheet,
2024-11-16 10:51:38 -05:00
self.location.row as i32,
self.location.col as i32,
2024-11-13 19:39:47 -05:00
)
2024-11-13 19:02:41 -05:00
.map_err(|s| anyhow!("Unable to format cell {}", s))?)
}
2024-11-16 10:51:38 -05:00
/// Update the current cell in a book.
/// This update won't be reflected until you call `Book::evaluate`.
pub fn edit_current_cell<S: Into<String>>(&mut self, value: S) -> Result<()> {
2024-12-08 20:34:08 -05:00
self.update_cell(&self.location.clone(), value)?;
2024-11-16 10:51:38 -05:00
Ok(())
}
/// Update an entry in the current sheet for a book.
/// This update won't be reflected until you call `Book::evaluate`.
2024-12-08 20:34:08 -05:00
pub fn update_cell<S: Into<String>>(&mut self, location: &Address, value: S) -> Result<()> {
2024-11-13 19:39:47 -05:00
self.model
.set_user_input(
self.current_sheet,
2024-11-16 10:51:38 -05:00
location.row as i32,
location.col as i32,
// TODO(jwall): This could probably be made more efficient
&value.into(),
2024-11-13 19:39:47 -05:00
)
.map_err(|e| anyhow!("Invalid cell contents: {}", e))?;
2024-11-13 19:02:41 -05:00
Ok(())
}
/// Insert `count` rows at a `row_idx`.
2024-11-16 10:51:38 -05:00
pub fn insert_rows(&mut self, row_idx: usize, count: usize) -> Result<()> {
for i in 0..count {
self.model
.insert_row(self.current_sheet, (row_idx + i) as i32)
.map_err(|e| anyhow!("Unable to insert row(s): {}", e))?;
}
2024-11-18 18:00:16 -05:00
if self.location.row >= row_idx {
self.move_to(&Address {
2024-11-18 18:00:16 -05:00
row: self.location.row + count,
col: self.location.col,
})?;
}
2024-11-16 20:25:35 -05:00
Ok(())
2024-11-16 10:51:38 -05:00
}
/// Insert `count` columns at a `col_idx`.
2024-11-16 10:51:38 -05:00
pub fn insert_columns(&mut self, col_idx: usize, count: usize) -> Result<()> {
for i in 0..count {
self.model
.insert_column(self.current_sheet, (col_idx + i) as i32)
.map_err(|e| anyhow!("Unable to insert column(s): {}", e))?;
}
2024-11-18 18:00:16 -05:00
if self.location.col >= col_idx {
self.move_to(&Address {
2024-11-18 18:00:16 -05:00
row: self.location.row,
col: self.location.col + count,
})?;
}
2024-11-16 20:25:35 -05:00
Ok(())
2024-11-16 10:51:38 -05:00
}
2024-11-13 19:02:41 -05:00
/// Get the current sheets dimensions. This is a somewhat expensive calculation.
2024-11-16 10:51:38 -05:00
pub fn get_dimensions(&self) -> Result<WorksheetDimension> {
Ok(self.get_sheet()?.dimension())
}
/// Get column size
pub fn get_col_size(&self, idx: usize) -> Result<usize> {
self.get_column_size_for_sheet(self.current_sheet, idx)
}
pub fn get_column_size_for_sheet(&self, sheet: u32, idx: usize) -> std::result::Result<usize, anyhow::Error> {
Ok((self.model.get_column_width(sheet, idx as i32)
2024-11-23 21:53:13 -05:00
.map_err(|e| anyhow!("Error getting column width: {:?}", e))?
/ COL_PIXELS) as usize)
}
pub fn set_col_size(&mut self, col: usize, width: usize) -> Result<()> {
self.set_column_size_for_sheet(self.current_sheet, col, width)
}
pub fn set_column_size_for_sheet(&mut self, sheet: u32, col: usize, width: usize) -> std::result::Result<(), anyhow::Error> {
self.model.set_column_width(sheet, col as i32, width as f64 * COL_PIXELS)
.map_err(|e| anyhow!("Error setting column width: {:?}", e))?;
Ok(())
}
2024-11-16 10:51:38 -05:00
// Get the size of the current sheet as a `(row_count, column_count)`
pub fn get_size(&self) -> Result<(usize, usize)> {
let sheet = &self.get_sheet()?.sheet_data;
let mut row_count = 0 as i32;
let mut col_count = 0 as i32;
for (ri, cols) in sheet.iter() {
2024-11-16 20:25:35 -05:00
row_count = max(*ri, row_count);
2024-11-16 10:51:38 -05:00
for (ci, _) in cols.iter() {
2024-11-16 20:25:35 -05:00
col_count = max(*ci, col_count);
2024-11-16 10:51:38 -05:00
}
}
Ok((row_count as usize, col_count as usize))
2024-11-13 19:02:41 -05:00
}
/// Select a sheet by name.
pub fn select_sheet_by_name(&mut self, name: &str) -> bool {
2024-11-29 09:44:53 -05:00
if let Some((idx, _sheet)) = self
.model.get_model()
2024-11-13 19:39:47 -05:00
.workbook
.worksheets
.iter()
2024-11-29 09:44:53 -05:00
.enumerate()
.find(|(_idx, sheet)| sheet.name == name)
2024-11-13 19:39:47 -05:00
{
2024-12-18 20:29:56 -05:00
self.current_sheet = idx as u32;
2024-11-13 19:02:41 -05:00
return true;
}
false
}
2024-11-13 19:49:31 -05:00
/// Get all sheet names
2024-11-13 19:02:41 -05:00
pub fn get_sheet_names(&self) -> Vec<String> {
self.model.get_model().workbook.get_worksheet_names()
2024-11-13 19:02:41 -05:00
}
2024-11-13 19:39:47 -05:00
2024-11-29 09:44:53 -05:00
pub fn select_next_sheet(&mut self) {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
let len = self.model.get_model().workbook.worksheets.len() as u32;
2024-11-29 09:44:53 -05:00
let mut next = self.current_sheet + 1;
if next == len {
next = 0;
}
self.current_sheet = next;
}
2024-12-18 20:29:56 -05:00
2024-11-29 09:44:53 -05:00
pub fn select_prev_sheet(&mut self) {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
let len = self.model.get_model().workbook.worksheets.len() as u32;
2024-11-29 09:44:53 -05:00
let next = if self.current_sheet == 0 {
len - 1
} else {
self.current_sheet - 1
};
self.current_sheet = next;
}
2024-11-13 19:02:41 -05:00
/// Select a sheet by id.
pub fn select_sheet_by_id(&mut self, id: u32) -> bool {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
2024-11-29 09:44:53 -05:00
if let Some((idx, _sheet)) = self
.model.get_model()
2024-11-13 19:39:47 -05:00
.workbook
.worksheets
.iter()
2024-11-29 09:44:53 -05:00
.enumerate()
.find(|(_idx, sheet)| sheet.sheet_id == id)
2024-11-13 19:39:47 -05:00
{
2024-11-29 09:44:53 -05:00
self.current_sheet = idx as u32;
2024-11-13 19:02:41 -05:00
return true;
}
false
}
2024-11-13 19:39:47 -05:00
/// Get the current `Worksheet`.
2024-11-16 10:51:38 -05:00
pub(crate) fn get_sheet(&self) -> Result<&Worksheet> {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
2024-11-13 19:39:47 -05:00
Ok(self
.model.get_model()
2024-11-13 19:39:47 -05:00
.workbook
.worksheet(self.current_sheet)
2024-11-29 09:44:53 -05:00
.map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?)
2024-11-11 19:27:59 -05:00
}
2024-11-23 21:53:13 -05:00
2024-12-04 20:02:08 -05:00
pub(crate) fn get_sheet_name_by_idx(&self, idx: usize) -> Result<&str> {
// TODO(jwall): Is there a cleaner way to do this with UserModel?
2024-12-04 20:02:08 -05:00
Ok(&self
.model.get_model()
2024-12-04 20:02:08 -05:00
.workbook
.worksheet(idx as u32)
2024-12-18 20:29:56 -05:00
.map_err(|s| anyhow!("Invalid Worksheet: {}", s))?
.name)
2024-12-04 20:02:08 -05:00
}
2024-11-16 10:51:38 -05:00
}
2024-11-13 19:39:47 -05:00
2024-11-16 10:51:38 -05:00
impl Default for Book {
fn default() -> Self {
2024-11-18 18:00:16 -05:00
let mut book =
Book::new(UserModel::new_empty("default_name", "en", "America/New_York").unwrap());
2024-12-08 20:34:08 -05:00
book.update_cell(&Address { row: 1, col: 1 }, "").unwrap();
2024-11-16 20:25:35 -05:00
book
2024-11-11 19:27:59 -05:00
}
}