From 2d6e45ad94494c6d78fd951f1c6847eb7e53e90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 19 Feb 2024 23:00:55 +0100 Subject: [PATCH] UPDATE: Adds a bunch of documentation and examples --- Cargo.lock | 2 +- Makefile | 10 +- README.md | 90 ++++++++-- base/Cargo.toml | 2 +- base/examples/hello_world.rs | 19 ++ base/src/lib.rs | 22 ++- base/src/model.rs | 263 ++++++++++++++++++++++++---- base/src/styles.rs | 4 +- base/src/test/test_styles.rs | 2 +- base/src/types.rs | 10 ++ xlsx/examples/hello_styles.rs | 11 +- xlsx/examples/widths_and_heights.rs | 8 +- xlsx/src/lib.rs | 14 +- 13 files changed, 381 insertions(+), 76 deletions(-) create mode 100644 base/examples/hello_world.rs diff --git a/Cargo.lock b/Cargo.lock index be20087..850add1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,7 +204,7 @@ dependencies = [ [[package]] name = "ironcalc_base" -version = "0.1.0" +version = "0.1.2" dependencies = [ "chrono", "chrono-tz", diff --git a/Makefile b/Makefile index ee06cc1..a97e613 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,15 @@ format: cargo fmt tests: lint - cargo test --verbose + cargo test + make remove-xlsx -clean: +remove-xlsx: + rm -f xlsx/hello-calc.xlsx + rm -f xlsx/hello-styles.xlsx + rm -f xlsx/widths-and-heights.xlsx + +clean: remove-xlsx cargo clean rm -r -f base/target rm -r -f xlsx/target diff --git a/README.md b/README.md index 9fb74cc..873ce56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 📚 IronCalc +# IronCalc [![MIT licensed][mit-badge]][mit-url] [![Apache 2.0 licensed][apache-badge]][apache-url] @@ -33,7 +33,7 @@ Programmed in Rust, you will be able to use it from a variety of programming lan We will build different _skins_: in the terminal, as a desktop application or use it in you own web application. -# 🛠️ Building +# Building ```bash cargo build --release @@ -41,37 +41,81 @@ cargo build --release # Testing, linting and code coverage -Testing: -```bash -cargo test -``` +Test are run automatically and test coverage can always be found in [codecov](https://codecov.io/gh/ironcalc/IronCalc) -Linting: -```bash -make lint -``` +If you want to run the tests yourself: -Testing and linting: ```bash make tests ``` -Code coverage: +Note that this runs unit tests, integration tests, linter tests and formatting tests. + +If you want to run the code coverage yourself: ```bash make coverage cd target/coverage/html/ python -m http.server ``` -# 🖹 API Documentation +# API Documentation -Documentation might be generated with +Documentation is published at: https://docs.rs/ironcalc/latest/ironcalc/ + +It might be generated locally ```bash -$ cargo doc --no-deps +$ make docs +$ cd target/doc +$ python -m http.server ``` -# 📝 ROADMAP +And visit + +# Simple example + +Add the dependency to `Cargo.toml`: +```toml +[dependencies] +ironcalc = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"} +``` + +And then use this code in `main.rs`: + +```rust +use ironcalc::{ + base::{expressions::utils::number_to_column, model::Model}, + export::save_to_xlsx, +}; + +fn main() -> Result<(), Box> { + let mut model = Model::new_empty("hello-calc.xlsx", "en", "UTC")?; + // Adds a square of numbers in the first sheet + for row in 1..100 { + for column in 1..100 { + let value = row * column; + model.set_user_input(0, row, column, format!("{}", value)); + } + } + // Adds a new sheet + model.add_sheet("Calculation")?; + // column 100 is CV + let last_column = number_to_column(100).unwrap(); + let formula = format!("=SUM(Sheet1!A1:{}100)", last_column); + model.set_user_input(1, 1, 1, formula); + + // evaluates + model.evaluate(); + + // saves to disk + save_to_xlsx(&model, "hello-calc.xlsx")?; + Ok(()) +} +``` + +See more examples in the `examples` folder of the xlsx crate. + +# ROADMAP > [!WARNING] > This is work-in-progress. IronCalc in developed in the open. Expect things to be broken and change quickly until version 0.5 @@ -83,7 +127,7 @@ Major milestones: MVP stands for _Minimum Viable Product_ -## Version 0.5 or MVP (15 March 2024) +## Version 0.5 or MVP (early 2024) Version 0.5 includes the engine, javascript and nodejs bindings and a web application @@ -123,8 +167,6 @@ Minor milestones in the ROADMAD for version 1.0.0 (engine and UI): * Python bindings * Full test coverage -I will be creating issues during the first two months of 2024 - # Early testing An early preview of the technology running entirely in your browser: @@ -132,6 +174,16 @@ An early preview of the technology running entirely in your browser: https://playground.ironcalc.com +# Collaborators needed!. Call to action + +We don't have a vibrant community just yet. This is the very stages of the project. But if you are passionate about code with high standards and no compromises, if you are looking for a project with high impact, if you are interested in a better, more open infrastructure for spreadsheets, whether you are a developer (rust, python, TypeScript, electron/tauri/anything else native app, React, you name it), a designer (we need a logo desperately!), an Excel power user who wants features, a business looking to integrate a MIT/Apache licensed spreadsheet in your own SaaS application join us! + +The best place to start will be to join or [discord channel](https://discord.gg/zZYWfh3RHJ) or sheet me an email at hello@ironcalc.com. + +Many have said it better before me: + + Folks wanted for hazardous journey. Low wages, bitter cold, long hours of complete darkness. Safe return doubtful. Honour and recognition in event of success. + # License diff --git a/base/Cargo.toml b/base/Cargo.toml index b321db5..27c11c4 100644 --- a/base/Cargo.toml +++ b/base/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironcalc_base" -version = "0.1.0" +version = "0.1.2" authors = ["Nicolás Hatcher "] edition = "2021" homepage = "https://www.ironcalc.com" diff --git a/base/examples/hello_world.rs b/base/examples/hello_world.rs new file mode 100644 index 0000000..21d2881 --- /dev/null +++ b/base/examples/hello_world.rs @@ -0,0 +1,19 @@ +use ironcalc_base::{cell::CellValue, model::Model}; + +fn main() -> Result<(), Box> { + let mut model = Model::new_empty("hello-world", "en", "UTC")?; + // A1 + model.set_user_input(0, 1, 1, "Hello".to_string()); + // B1 + model.set_user_input(0, 1, 2, "world!".to_string()); + // C1 + model.set_user_input(0, 1, 3, "=CONCAT(A1, \" \", B1".to_string()); + // evaluates + model.evaluate(); + + assert_eq!( + model.get_cell_value_by_index(0, 1, 3), + Ok(CellValue::String("Hello world!".to_string())) + ); + Ok(()) +} diff --git a/base/src/lib.rs b/base/src/lib.rs index ee8d351..2e4dd72 100644 --- a/base/src/lib.rs +++ b/base/src/lib.rs @@ -1,4 +1,24 @@ -#![deny(clippy::unwrap_used)] +//! # IronCalcBase engine API +//! +//! This is the documentation for the base engine API. +//! +//! # Basic usage +//! +//! Add the dependency in Cargo.toml: +//! +//! ```toml +//! [dependencies] +//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"} +//! ``` +//! +//! until version 0.5.0 you should use the git dependencies as stated +//! +//! In this example we use the excel function `CONCAT` to concatenate strings in cells `A1` and `B1`: +//! +//! ```rust +#![doc = include_str!("../examples/hello_world.rs")] +//! ``` + pub mod calc_result; pub mod cell; pub mod expressions; diff --git a/base/src/model.rs b/base/src/model.rs index d40726d..550588c 100644 --- a/base/src/model.rs +++ b/base/src/model.rs @@ -1,4 +1,12 @@ -use serde::{Deserialize, Serialize}; +#![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; @@ -39,6 +47,7 @@ pub use chrono_tz::Tz; #[cfg(test)] pub use crate::mock_time::get_milliseconds_since_epoch; +/// wasm implementation for time #[cfg(not(test))] #[cfg(not(target_arch = "wasm32"))] pub fn get_milliseconds_since_epoch() -> i64 { @@ -56,55 +65,72 @@ pub fn get_milliseconds_since_epoch() -> i64 { Date::now() as i64 } +/// A cell might be evaluated or being evaluated #[derive(Clone)] pub enum CellState { + /// The cell has already been evaluated Evaluated, + /// The cell is being evaluated Evaluating, } +/// A parsed formula for a defined name pub enum ParsedDefinedName { + /// CellReference (`=C4`) CellReference(CellReference), + /// A Range (`=C4:D6`) RangeReference(Range), + /// `=SomethingElse` InvalidDefinedNameFormula, // TODO: Support constants in defined names // TODO: Support formulas in defined names // TODO: Support tables in defined names } -/// A model includes: -/// * A Workbook: An internal representation of and Excel workbook -/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only) -/// * A list of cells with its status (evaluating, evaluated, not evaluated) -/// * A dictionary with the shared strings and their indices. -/// This is an optimization for large files (~1 million rows) +/// A dynamical IronCalc model. +/// +/// Its is composed of a `Workbook`. Everything else are dynamical quantities: +/// +/// * The Locale: a parsed version of the Workbook's locale +/// * The Timezone: an object representing the Workbook's timezone +/// * The language. Note that the timezone and the locale belong to the workbook while +/// the language can be different for different users looking _at the same_ workbook. +/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only) +/// * A list of cells with its status (evaluating, evaluated, not evaluated) +/// * A dictionary with the shared strings and their indices. +/// This is an optimization for large files (~1 million rows) pub struct Model { + /// A Rust internal representation of an Excel workbook pub workbook: Workbook, + /// A list of parsed formulas pub parsed_formulas: Vec>, + /// A list of parsed defined names pub parsed_defined_names: HashMap<(Option, String), ParsedDefinedName>, + /// An optimization to lookup strings faster pub shared_strings: HashMap, + /// An instance of the parser pub parser: Parser, + /// The list of cells with formulas that are evaluated of being evaluated pub cells: HashMap<(u32, i32, i32), CellState>, + /// The locale of the model pub locale: Locale, + /// Tha language used pub language: Language, + /// The timezone used to evaluate the model pub tz: Tz, } +// FIXME: Maybe this should be the same as CellReference +/// A struct pointing to a cell pub struct CellIndex { + /// Sheet index (0-indexed) pub index: u32, + /// Row index pub row: i32, + /// Column index pub column: i32, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Style { - pub alignment: Option, - pub num_fmt: String, - pub fill: Fill, - pub font: Font, - pub border: Border, - pub quote_prefix: bool, -} - impl Model { pub(crate) fn evaluate_node_with_reference( &mut self, @@ -625,12 +651,27 @@ impl Model { } } + /// Sets the color of the sheet tab + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// assert_eq!(model.workbook.worksheet(0)?.color, None); + /// model.set_sheet_color(0, "#DBBE29")?; + /// assert_eq!(model.workbook.worksheet(0)?.color, Some("#DBBE29".to_string())); + /// # Ok(()) + /// # } + /// ``` pub fn set_sheet_color(&mut self, sheet: u32, color: &str) -> Result<(), String> { let worksheet = self.workbook.worksheet_mut(sheet)?; if color.is_empty() { worksheet.color = None; return Ok(()); - } else if common::is_valid_hex_color(color) { + } + if common::is_valid_hex_color(color) { worksheet.color = Some(color.to_string()); return Ok(()); } @@ -677,11 +718,22 @@ impl Model { } } - /// Returns true if cell is completely empty. - /// Cell with formula that evaluates to empty string is not considered empty. + /// Returns `true` if the cell is completely empty. + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// assert_eq!(model.is_empty_cell(0, 1, 1)?, true); + /// model.set_user_input(0, 1, 1, "Attention is all you need".to_string()); + /// assert_eq!(model.is_empty_cell(0, 1, 1)?, false); + /// # Ok(()) + /// # } + /// ``` pub fn is_empty_cell(&self, sheet: u32, row: i32, column: i32) -> Result { - let worksheet = self.workbook.worksheet(sheet)?; - worksheet.is_empty_cell(row, column) + self.workbook.worksheet(sheet)?.is_empty_cell(row, column) } pub(crate) fn evaluate_cell(&mut self, cell_reference: CellReference) -> CalcResult { @@ -743,14 +795,48 @@ impl Model { None } - // Public API /// Returns a model from a String representation of a workbook + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # use ironcalc_base::cell::CellValue; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// model.set_user_input(0, 1, 1, "Stella!".to_string()); + /// let model2 = Model::from_json(&model.to_json_str())?; + /// assert_eq!( + /// model2.get_cell_value_by_index(0, 1, 1), + /// Ok(CellValue::String("Stella!".to_string())) + /// ); + /// # Ok(()) + /// # } + /// ``` pub fn from_json(s: &str) -> Result { let workbook: Workbook = serde_json::from_str(s).map_err(|_| "Error parsing workbook".to_string())?; Model::from_workbook(workbook) } + /// Returns a model from a Workbook object + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # use ironcalc_base::cell::CellValue; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// model.set_user_input(0, 1, 1, "Stella!".to_string()); + /// let model2 = Model::from_workbook(model.workbook)?; + /// assert_eq!( + /// model2.get_cell_value_by_index(0, 1, 1), + /// Ok(CellValue::String("Stella!".to_string())) + /// ); + /// # Ok(()) + /// # } + /// ``` pub fn from_workbook(workbook: Workbook) -> Result { let parsed_formulas = Vec::new(); let worksheets = &workbook.worksheets; @@ -803,6 +889,20 @@ impl Model { } /// Parses a reference like "Sheet1!B4" into {0, 2, 4} + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # use ironcalc_base::calc_result::CellReference; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// model.set_user_input(0, 1, 1, "Stella!".to_string()); + /// let reference = model.parse_reference("Sheet1!D40"); + /// assert_eq!(reference, Some(CellReference {sheet: 0, row: 40, column: 4})); + /// # Ok(()) + /// # } + /// ``` pub fn parse_reference(&self, s: &str) -> Option { let bytes = s.as_bytes(); let mut sheet_name = "".to_string(); @@ -857,7 +957,23 @@ impl Model { Some(CellReference { sheet, row, column }) } - /// moves the value in area from source to target. + /// Moves the formula `value` from `source` (in `area`) to `target`. + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex}; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// let source = CellReferenceIndex { sheet: 0, row: 3, column: 1}; + /// let target = CellReferenceIndex { sheet: 0, row: 50, column: 1}; + /// let area = Area { sheet: 0, row: 1, column: 1, width: 5, height: 4}; + /// let result = model.move_cell_value_to_area("=B1", &source, &target, &area)?; + /// assert_eq!(&result, "=B48"); + /// # Ok(()) + /// # } + /// ``` pub fn move_cell_value_to_area( &mut self, value: &str, @@ -908,7 +1024,22 @@ impl Model { } } - /// 'Extends' the value from cell [sheet, row, column] to [target_row, target_column] + /// 'Extends' the value from cell (`sheet`, `row`, `column`) to (`target_row`, `target_column`) in the same sheet + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// let (sheet, row, column) = (0, 1, 1); + /// model.set_user_input(sheet, row, column, "=B1*D4".to_string()); + /// let (target_row, target_column) = (30, 1); + /// let result = model.extend_to(sheet, row, column, target_row, target_column)?; + /// assert_eq!(&result, "=B30*D33"); + /// # Ok(()) + /// # } + /// ``` pub fn extend_to( &self, sheet: u32, @@ -936,14 +1067,32 @@ impl Model { Ok(result) } - /// 'Extends' value from cell [sheet, row, column] to [target_row, target_column] + /// 'Extends' the formula `value` from `source` to `target` + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # use ironcalc_base::expressions::types::CellReferenceIndex; + /// # fn main() -> Result<(), Box> { + /// let mut model = Model::new_empty("model", "en", "UTC")?; + /// let source = CellReferenceIndex {sheet: 0, row: 1, column: 1}; + /// let target = CellReferenceIndex {sheet: 0, row: 30, column: 1}; + /// let result = model.extend_copied_value("=B1*D4", &source, &target)?; + /// assert_eq!(&result, "=B30*D33"); + /// # Ok(()) + /// # } + /// ``` pub fn extend_copied_value( - &mut self, // FIXME: weird that it must be mutable + &mut self, value: &str, - source_sheet_name: &str, source: &CellReferenceIndex, target: &CellReferenceIndex, ) -> Result { + let source_sheet_name = match self.workbook.worksheets.get(source.sheet as usize) { + Some(ws) => ws.get_name(), + None => { + return Err("Invalid worksheet index".to_owned()); + } + }; let target_sheet_name = match self.workbook.worksheets.get(target.sheet as usize) { Some(ws) => ws.get_name(), None => { @@ -967,6 +1116,20 @@ impl Model { Ok(value.to_string()) } + /// Returns the formula in (`sheet`, `row`, `column`) if any + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # fn main() -> Result<(), Box> { + /// 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)?; + /// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string())); + /// # Ok(()) + /// # } + /// ``` pub fn cell_formula( &self, sheet: u32, @@ -1210,6 +1373,9 @@ impl Model { } /// Gets the Excel Value (Bool, Number, String) of a cell + /// + /// See also: + /// * [Model::get_cell_value_by_index()] pub fn get_cell_value_by_ref(&self, cell_ref: &str) -> Result { let cell_reference = match self.parse_reference(cell_ref) { Some(c) => c, @@ -1222,6 +1388,10 @@ impl Model { self.get_cell_value_by_index(sheet_index, row, column) } + /// Returns the cell value for (`sheet`, `row`, `column`) + /// + /// See also: + /// * [Model::formatted_cell_value()] pub fn get_cell_value_by_index( &self, sheet_index: u32, @@ -1238,6 +1408,26 @@ impl Model { Ok(cell_value) } + /// Returns the formatted cell value for (`sheet`, `row`, `column`) + /// + /// See also: + /// * [Model::get_cell_value_by_index()] + /// * [Model::get_cell_value_by_ref] + /// + /// # Examples + /// + /// ```rust + /// # use ironcalc_base::model::Model; + /// # fn main() -> Result<(), Box> { + /// 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)?; + /// assert_eq!(result, "0.333333333".to_string()); + /// # Ok(()) + /// # } + /// ``` pub fn formatted_cell_value( &self, sheet_index: u32, @@ -1337,6 +1527,7 @@ impl Model { Ok(()) } + /// Returns the style index for cell (`sheet`, `row`, `column`) pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> i32 { // First check the cell, then row, the column let cell = self @@ -1370,6 +1561,7 @@ impl Model { } } + /// Returns the style for cell (`sheet`, `row`, `column`) pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Style { self.workbook .styles @@ -1415,6 +1607,9 @@ impl Model { Ok(rows.join("\n")) } + /// Sets the currency of the model. + /// Currently we only support `USD`, `EUR`, `GBP` and `JPY` + /// NB: This is not preserved in the JSON. pub fn set_currency(&mut self, iso: &str) -> Result<(), &str> { // TODO: Add a full list let symbol = if iso == "USD" { @@ -1435,6 +1630,7 @@ impl Model { Ok(()) } + /// Returns the number of frozen rows in `sheet` pub fn get_frozen_rows(&self, sheet: u32) -> Result { if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) { Ok(worksheet.frozen_rows) @@ -1443,6 +1639,7 @@ impl Model { } } + /// Return the number of frozen columns in `sheet` pub fn get_frozen_columns(&self, sheet: u32) -> Result { if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) { Ok(worksheet.frozen_columns) @@ -1451,11 +1648,14 @@ impl Model { } } + /// Sets the number of frozen rows to `frozen_rows` in the workbook. + /// Fails if `frozen`_rows` is either too small (<0) or too large (>LAST_ROW)` pub fn set_frozen_rows(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> { if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) { if frozen_rows < 0 { return Err("Frozen rows cannot be negative".to_string()); - } else if frozen_rows >= LAST_ROW { + } + if frozen_rows >= LAST_ROW { return Err("Too many rows".to_string()); } worksheet.frozen_rows = frozen_rows; @@ -1465,11 +1665,14 @@ impl Model { } } + /// Sets the number of frozen columns to `frozen_column` in the workbook. + /// Fails if `frozen`_columns` is either too small (<0) or too large (>LAST_COLUMN)` pub fn set_frozen_columns(&mut self, sheet: u32, frozen_columns: i32) -> Result<(), String> { if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) { if frozen_columns < 0 { return Err("Frozen columns cannot be negative".to_string()); - } else if frozen_columns >= LAST_COLUMN { + } + if frozen_columns >= LAST_COLUMN { return Err("Too many columns".to_string()); } worksheet.frozen_columns = frozen_columns; diff --git a/base/src/styles.rs b/base/src/styles.rs index ba00256..ba75068 100644 --- a/base/src/styles.rs +++ b/base/src/styles.rs @@ -1,7 +1,7 @@ use crate::{ - model::{Model, Style}, + model::Model, number_format::{get_default_num_fmt_id, get_new_num_fmt_index, get_num_fmt}, - types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Styles}, + types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles}, }; // TODO: Move Styles and all related types from crate::types here diff --git a/base/src/test/test_styles.rs b/base/src/test/test_styles.rs index c6f41f9..31cd71a 100644 --- a/base/src/test/test_styles.rs +++ b/base/src/test/test_styles.rs @@ -1,7 +1,7 @@ #![allow(clippy::unwrap_used)] -use crate::model::Style; use crate::test::util::new_empty_model; +use crate::types::Style; #[test] fn test_model_set_cells_with_values_styles() { diff --git a/base/src/types.rs b/base/src/types.rs index 21158fc..914351b 100644 --- a/base/src/types.rs +++ b/base/src/types.rs @@ -314,6 +314,16 @@ impl Default for Styles { } } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Style { + pub alignment: Option, + pub num_fmt: String, + pub fill: Fill, + pub font: Font, + pub border: Border, + pub quote_prefix: bool, +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct NumFmt { pub num_fmt_id: i32, diff --git a/xlsx/examples/hello_styles.rs b/xlsx/examples/hello_styles.rs index 9157437..3fbc498 100644 --- a/xlsx/examples/hello_styles.rs +++ b/xlsx/examples/hello_styles.rs @@ -4,18 +4,13 @@ fn main() -> Result<(), Box> { let mut model = Model::new_empty("hello_styles", "en", "UTC")?; // We are going to change styles in cell A1 - let sheet = 0; - let row = 1; - let column = 1; + let (sheet, row, column) = (0, 1, 1); let mut style = model.get_style_for_cell(sheet, row, column); - style.fill.fg_color = Some("#FFEE11".to_string()); + style.fill.fg_color = Some("#FF9011".to_string()); style.font.b = true; - style.font.color = Some("#EEFF22".to_string()); + style.font.color = Some("#E91E63".to_string()); model.set_cell_style(sheet, row, column, &style)?; - // evaluates (unnecessary in this case) - model.evaluate(); - // saves to disk save_to_xlsx(&model, "hello-styles.xlsx")?; Ok(()) diff --git a/xlsx/examples/widths_and_heights.rs b/xlsx/examples/widths_and_heights.rs index 24eabb6..f6409c8 100644 --- a/xlsx/examples/widths_and_heights.rs +++ b/xlsx/examples/widths_and_heights.rs @@ -3,10 +3,9 @@ use ironcalc::{base::model::Model, export::save_to_xlsx}; fn main() -> Result<(), Box> { let mut model = Model::new_empty("widths-and-heights", "en", "UTC")?; // Cell C5 - let column = 3; - let row = 5; + let (sheet, row, column) = (0, 5, 3); // Make the first column 4 times as width - let worksheet = model.workbook.worksheet_mut(0)?; + let worksheet = model.workbook.worksheet_mut(sheet)?; let column_width = worksheet.column_width(column)? * 4.0; worksheet.set_column_width(column, column_width)?; @@ -14,9 +13,6 @@ fn main() -> Result<(), Box> { let row_height = worksheet.row_height(row)? * 2.0; worksheet.set_row_height(row, row_height)?; - // evaluates - model.evaluate(); - // saves to disk save_to_xlsx(&model, "widths-and-heights.xlsx")?; Ok(()) diff --git a/xlsx/src/lib.rs b/xlsx/src/lib.rs index 0e8cc95..05365aa 100644 --- a/xlsx/src/lib.rs +++ b/xlsx/src/lib.rs @@ -1,7 +1,7 @@ //! # IronCalc - Core API documentation //! -//! This technical API documentation in aimed at developers who want to develop bindings for a different language, -//! build a UI based on the engine or just use the library in a Rust program +//! This technical API documentation is aimed at developers. +//! It is used to build language bindings (like python, javascript or nodejs) or to build full fledged applications like ironCalc in the terminal or IronCalc, the Web application. //! //! ## Basic usage //! @@ -21,7 +21,12 @@ #![doc = include_str!("../examples/hello_calc.rs")] //! ``` //! -//! ## Styling the workbook +//! ## Examples +//! +//! This is a collection of full fledged examples you can use as a starting point or for learning purposes. +//! You might find the code in the examples folder +//! +//! ### Styling the workbook //! //! Adding colors, to cells, full columns or full rows is easy //! @@ -29,12 +34,11 @@ #![doc = include_str!("../examples/hello_styles.rs")] //! ``` //! -//! Changing column width and row heigh +//! ### Changing column width and row heigh //! //! ```rust #![doc = include_str!("../examples/widths_and_heights.rs")] //! ``` -//! #![doc( html_logo_url = "https://raw.githubusercontent.com/ironcalc/ironcalc/main/assets/logo.png",