UPDATE: Adds 'user model' API (#27)

* bump version for documentation
* Fixes wrong doc comment
* renames old APIs to be consistent
This commit is contained in:
Nicolás Hatcher Andrés
2024-04-03 22:41:15 +02:00
committed by GitHub
parent e9fc41541b
commit d445553d85
45 changed files with 3233 additions and 268 deletions

View File

@@ -1,12 +1,5 @@
#![deny(missing_docs)]
//! # Model
//!
//! Note that sheets are 0-indexed and rows and columns are 1-indexed.
//!
//! IronCalc is row first. A cell is referenced by (`sheet`, `row`, `column`)
//!
use serde_json::json;
use std::collections::HashMap;
@@ -47,7 +40,11 @@ use chrono_tz::Tz;
#[cfg(test)]
pub use crate::mock_time::get_milliseconds_since_epoch;
/// wasm implementation for time
/// Number of milliseconds since January 1, 1970
/// Used by time and date functions. It takes the value from the environment:
/// * The Operative System
/// * The JavaScript environment
/// * Or mocked for tests
#[cfg(not(test))]
#[cfg(not(target_arch = "wasm32"))]
pub fn get_milliseconds_since_epoch() -> i64 {
@@ -67,7 +64,7 @@ pub fn get_milliseconds_since_epoch() -> i64 {
/// A cell might be evaluated or being evaluated
#[derive(Clone)]
pub enum CellState {
pub(crate) enum CellState {
/// The cell has already been evaluated
Evaluated,
/// The cell is being evaluated
@@ -75,7 +72,7 @@ pub enum CellState {
}
/// A parsed formula for a defined name
pub enum ParsedDefinedName {
pub(crate) enum ParsedDefinedName {
/// CellReference (`=C4`)
CellReference(CellReferenceIndex),
/// A Range (`=C4:D6`)
@@ -105,19 +102,19 @@ pub struct Model {
/// A list of parsed formulas
pub parsed_formulas: Vec<Vec<Node>>,
/// A list of parsed defined names
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
pub(crate) parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
/// An optimization to lookup strings faster
pub shared_strings: HashMap<String, usize>,
pub(crate) shared_strings: HashMap<String, usize>,
/// An instance of the parser
pub parser: Parser,
pub(crate) parser: Parser,
/// The list of cells with formulas that are evaluated of being evaluated
pub cells: HashMap<(u32, i32, i32), CellState>,
pub(crate) cells: HashMap<(u32, i32, i32), CellState>,
/// The locale of the model
pub locale: Locale,
pub(crate) locale: Locale,
/// Tha language used
pub language: Language,
pub(crate) language: Language,
/// The timezone used to evaluate the model
pub tz: Tz,
pub(crate) tz: Tz,
}
// FIXME: Maybe this should be the same as CellReference
@@ -659,7 +656,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
@@ -726,7 +723,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
@@ -803,7 +800,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -827,7 +824,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -896,7 +893,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -965,7 +962,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1036,7 +1033,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1083,7 +1080,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1138,13 +1135,13 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
/// model.evaluate();
/// let result = model.cell_formula(sheet, row, column)?;
/// let result = model.get_cell_formula(sheet, row, column)?;
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
/// # Ok(())
/// # }
@@ -1152,24 +1149,33 @@ impl Model {
///
/// See also:
/// * [Model::get_cell_content()]
pub fn cell_formula(
pub fn get_cell_formula(
&self,
sheet: u32,
row: i32,
column: i32,
) -> Result<Option<String>, String> {
let worksheet = self.workbook.worksheet(sheet)?;
Ok(worksheet.cell(row, column).and_then(|cell| {
cell.get_formula().map(|formula_index| {
let formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
let cell_ref = CellReferenceRC {
sheet: worksheet.get_name(),
row,
column,
};
format!("={}", to_string(formula, &cell_ref))
})
}))
match worksheet.cell(row, column) {
Some(cell) => match cell.get_formula() {
Some(formula_index) => {
let formula = &self
.parsed_formulas
.get(sheet as usize)
.ok_or("missing sheet")?
.get(formula_index as usize)
.ok_or("missing formula")?;
let cell_ref = CellReferenceRC {
sheet: worksheet.get_name(),
row,
column,
};
Ok(Some(format!("={}", to_string(formula, &cell_ref))))
}
None => Ok(None),
},
None => Ok(None),
}
}
/// Updates the value of a cell with some text
@@ -1178,7 +1184,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1221,7 +1227,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1258,7 +1264,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1296,7 +1302,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1348,7 +1354,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1358,7 +1364,7 @@ impl Model {
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
/// model.evaluate();
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
/// assert_eq!(model.formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
/// assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
/// # Ok(())
/// # }
/// ```
@@ -1529,7 +1535,7 @@ impl Model {
/// Returns the cell value for (`sheet`, `row`, `column`)
///
/// See also:
/// * [Model::formatted_cell_value()]
/// * [Model::get_formatted_cell_value()]
pub fn get_cell_value_by_index(
&self,
sheet_index: u32,
@@ -1555,35 +1561,34 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
/// model.evaluate();
/// let result = model.formatted_cell_value(sheet, row, column)?;
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "0.333333333".to_string());
/// # Ok(())
/// # }
/// ```
pub fn formatted_cell_value(
pub fn get_formatted_cell_value(
&self,
sheet_index: u32,
row: i32,
column: i32,
) -> Result<String, String> {
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
let cell = self
.workbook
.worksheet(sheet_index)?
.cell(row, column)
.cloned()
.unwrap_or_default();
let formatted_value =
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
format_number(value, &format, &self.locale).text
});
Ok(formatted_value)
match self.workbook.worksheet(sheet_index)?.cell(row, column) {
Some(cell) => {
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
let formatted_value =
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
format_number(value, &format, &self.locale).text
});
Ok(formatted_value)
}
None => Ok("".to_string()),
}
}
/// Returns a string with the cell content. If there is a formula returns the formula
@@ -1647,15 +1652,53 @@ impl Model {
}
}
/// Sets cell to empty. Can be used to delete value without affecting style.
pub fn set_cell_empty(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?;
worksheet.set_cell_empty(row, column);
/// Removes the content of the cell but leaves the style.
///
/// See also:
/// * [Model::cell_clear_all()]
///
/// # Examples
///
/// ```rust
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "100$".to_string());
/// model.cell_clear_contents(sheet, row, column);
/// model.set_user_input(sheet, row, column, "10".to_string());
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "10$".to_string());
/// # Ok(())
/// # }
/// ```
pub fn cell_clear_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.cell_clear_contents(row, column);
Ok(())
}
/// Deletes a cell by removing it from worksheet data.
pub fn delete_cell(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
/// Deletes a cell by removing it from worksheet data. All content and style is removed.
///
/// See also:
/// * [Model::cell_clear_contents()]
///
/// # Examples
///
/// ```rust
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "100$".to_string());
/// model.cell_clear_all(sheet, row, column);
/// model.set_user_input(sheet, row, column, "10".to_string());
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "10".to_string());
/// # Ok(())
/// # }
pub fn cell_clear_all(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?;
let sheet_data = &mut worksheet.sheet_data;
@@ -1682,9 +1725,8 @@ impl Model {
if r.r == row {
if r.custom_format {
return r.s;
} else {
break;
}
break;
}
}
let cols = &self.workbook.worksheets[sheet as usize].cols;
@@ -1718,8 +1760,22 @@ impl Model {
}
}
/// Returns data about the worksheets
pub fn get_sheets_info(&self) -> Vec<SheetInfo> {
self.workbook
.worksheets
.iter()
.map(|worksheet| SheetInfo {
name: worksheet.get_name(),
state: worksheet.state.to_string(),
color: worksheet.color.clone(),
sheet_id: worksheet.sheet_id,
})
.collect()
}
/// Returns markup representation of the given `sheet`.
pub fn sheet_markup(&self, sheet: u32) -> Result<String, String> {
pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
let worksheet = self.workbook.worksheet(sheet)?;
let dimension = worksheet.dimension();
@@ -1729,9 +1785,9 @@ impl Model {
let mut row_markup: Vec<String> = Vec::new();
for column in 1..(dimension.max_column + 1) {
let mut cell_markup = match self.cell_formula(sheet, row, column)? {
let mut cell_markup = match self.get_cell_formula(sheet, row, column)? {
Some(formula) => formula,
None => self.formatted_cell_value(sheet, row, column)?,
None => self.get_formatted_cell_value(sheet, row, column)?,
};
let style = self.get_style_for_cell(sheet, row, column);
if style.font.b {
@@ -1770,7 +1826,7 @@ impl Model {
}
/// Returns the number of frozen rows in `sheet`
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
Ok(worksheet.frozen_rows)
} else {
@@ -1779,7 +1835,7 @@ impl Model {
}
/// Return the number of frozen columns in `sheet`
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
Ok(worksheet.frozen_columns)
} else {
@@ -1820,6 +1876,34 @@ impl Model {
Err("Invalid sheet".to_string())
}
}
/// Returns the width of a column
#[inline]
pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
self.workbook.worksheet(sheet)?.get_column_width(column)
}
/// Sets the width of a column
#[inline]
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.set_column_width(column, width)
}
/// Returns the height of a row
#[inline]
pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
self.workbook.worksheet(sheet)?.row_height(row)
}
/// Sets the height of a row
#[inline]
pub fn set_row_height(&mut self, sheet: u32, column: i32, height: f64) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.set_row_height(column, height)
}
}
#[cfg(test)]