Compare commits
46 Commits
v0.3.0
...
feature/ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3f0f33c2 | ||
|
|
1ff0c38aa5 | ||
|
|
e5a2db4d8c | ||
|
|
fc7335707a | ||
|
|
4095b7db6e | ||
|
|
dd9ca4224d | ||
|
|
5aa7617e97 | ||
|
|
a10d1f4615 | ||
|
|
1e8441a674 | ||
|
|
b2c5027f56 | ||
|
|
91984dc920 | ||
|
|
74be62823d | ||
|
|
edd00096b6 | ||
|
|
d764752f16 | ||
|
|
ce6c908dc7 | ||
|
|
6ee450709a | ||
|
|
23ab5dfef2 | ||
|
|
7e54cb6aa2 | ||
|
|
857ebabf16 | ||
|
|
f0af3048b7 | ||
|
|
99125f1fea | ||
|
|
f96481feb8 | ||
|
|
dc8bb6da21 | ||
|
|
d866e283e9 | ||
|
|
8a54f45d75 | ||
|
|
42d557d485 | ||
|
|
293f7c6de6 | ||
|
|
38325b0bb9 | ||
|
|
282ed16f0d | ||
|
|
fd744d28a3 | ||
|
|
9a717daf04 | ||
|
|
84bf859c2c | ||
|
|
e57101f279 | ||
|
|
264fcac63c | ||
|
|
7777f8e5d6 | ||
|
|
6aa73171c7 | ||
|
|
8051913b2d | ||
|
|
cfa38548d5 | ||
|
|
9787721c5a | ||
|
|
610b899f66 | ||
|
|
24fb87721f | ||
|
|
d3bc8b135c | ||
|
|
0f6d311de2 | ||
|
|
0c15ae194d | ||
|
|
20c4a596bf | ||
|
|
f07a69260f |
11
.github/workflows/npm.yml
vendored
11
.github/workflows/npm.yml
vendored
@@ -12,11 +12,7 @@ permissions:
|
||||
publish:
|
||||
description: "Publish to npm"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- yes
|
||||
- no
|
||||
default: "no"
|
||||
type: boolean
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./bindings/nodejs
|
||||
@@ -437,10 +433,11 @@ jobs:
|
||||
shell: bash
|
||||
- name: Publish
|
||||
run: |
|
||||
npm config set provenance true
|
||||
if [ "${{ github.event.inputs.publish }}" = "yes" ]; then
|
||||
echo "${{ github.event.inputs.publish }}"
|
||||
if [ "${{ github.event.inputs.publish }}" = "true" ]; then
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
npm publish --access public
|
||||
echo "Published to npm"
|
||||
else
|
||||
echo "Not a release, skipping publish"
|
||||
fi
|
||||
|
||||
143
.github/workflows/pypi.yml
vendored
Normal file
143
.github/workflows/pypi.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
name: Upload component to Python Package Index
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release:
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
description: "Release? If false, publish to test.pypi.org, if true, publish to pypi.org"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
working-directory: bindings/python
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: ${{ matrix.target }}
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter
|
||||
sccache: 'true'
|
||||
working-directory: bindings/python
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter
|
||||
sccache: 'true'
|
||||
working-directory: bindings/python
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
working-directory: bindings/python
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/dist
|
||||
|
||||
publish-to-test-pypi:
|
||||
if: ${{ github.event.inputs.release != 'true' }}
|
||||
name: >-
|
||||
Publish Python 🐍 distribution 📦 to Test PyPI
|
||||
runs-on: ubuntu-latest
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/
|
||||
- name: Publish distribution 📦 to Test PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TEST_API_TOKEN }}
|
||||
MATURIN_REPOSITORY_URL: "https://test.pypi.org/legacy/"
|
||||
with:
|
||||
command: upload
|
||||
args: --skip-existing *
|
||||
working-directory: bindings/python
|
||||
|
||||
publish-pypi:
|
||||
if: ${{ github.event.inputs.release == 'true' }}
|
||||
name: >-
|
||||
Publish Python 🐍 distribution 📦 to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: bindings/python/
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
MATURIN_REPOSITORY_URL: "https://upload.pypi.org/legacy/"
|
||||
with:
|
||||
command: upload
|
||||
args: --skip-existing *
|
||||
working-directory: bindings/python
|
||||
@@ -8,12 +8,20 @@
|
||||
- New document server (Thanks Dani!)
|
||||
- New function FORMULATEXT
|
||||
- Name Manager ([#212](https://github.com/ironcalc/IronCalc/pull/212) [#220](https://github.com/ironcalc/IronCalc/pull/220))
|
||||
- Add context menu. We can now insert rows and columns. Freeze and unfreeze rows and columns. Delete rows and columns [#271]
|
||||
- Add nodejs bindings [#254]
|
||||
- Add python bindings for all platforms
|
||||
- Add is split into the product and widget
|
||||
- Add Python documentation [#260]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed several issues with pasting content
|
||||
- Fixed several issues with borders
|
||||
- Fixed bug where columns and rows could be resized to negative width and height, respectively
|
||||
- Undo/redo when add/delete sheet now works [#270]
|
||||
- Numerous small fixes
|
||||
- Multiple fixes to the documentation
|
||||
|
||||
## [0.2.0] - 2024-11-06 (The HN release)
|
||||
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -448,7 +448,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ironcalc_nodejs"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"ironcalc",
|
||||
"napi",
|
||||
@@ -1070,7 +1070,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
dependencies = [
|
||||
"ironcalc_base",
|
||||
"serde",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "ironcalc_base"
|
||||
version = "0.3.0"
|
||||
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
homepage = "https://www.ironcalc.com"
|
||||
repository = "https://github.com/ironcalc/ironcalc/"
|
||||
description = "Open source spreadsheet engine"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ironcalc_base::{types::CellType, Model};
|
||||
use ironcalc_base::{Model, types::CellType};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ironcalc_base::{cell::CellValue, Model};
|
||||
use ironcalc_base::{Model, cell::CellValue};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
||||
|
||||
@@ -136,6 +136,33 @@ impl Model {
|
||||
}),
|
||||
);
|
||||
|
||||
// In the list of columns:
|
||||
// * Keep all the columns to the left
|
||||
// * Displace all the columns to the right
|
||||
|
||||
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||
|
||||
let mut new_columns = Vec::new();
|
||||
for col in worksheet.cols.iter_mut() {
|
||||
// range under study
|
||||
let min = col.min;
|
||||
let max = col.max;
|
||||
if column > max {
|
||||
// If the range under study is to our left, this is a noop
|
||||
} else if column <= min {
|
||||
// If the range under study is to our right, we displace it
|
||||
col.min = min + column_count;
|
||||
col.max = max + column_count;
|
||||
} else {
|
||||
// If the range under study is in the middle we augment it
|
||||
col.max = max + column_count;
|
||||
}
|
||||
new_columns.push(col.clone());
|
||||
}
|
||||
// TODO: If in a row the cell to the right and left have the same style we should copy it
|
||||
|
||||
worksheet.cols = new_columns;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
expressions::{
|
||||
parser::{
|
||||
move_formula::ref_is_in_area,
|
||||
stringify::{to_string, to_string_displaced, DisplaceData},
|
||||
stringify::{DisplaceData, to_string, to_string_displaced},
|
||||
walk::forward_references,
|
||||
},
|
||||
types::{Area, CellReferenceIndex, CellReferenceRC},
|
||||
|
||||
@@ -149,14 +149,16 @@ impl Lexer {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
return Err(self
|
||||
.set_error(&format!("Failed parsing row {}", row_left), position))
|
||||
.set_error(&format!("Failed parsing row {}", row_left), position));
|
||||
}
|
||||
};
|
||||
let row_right = match row_right.parse::<i32>() {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
return Err(self
|
||||
.set_error(&format!("Failed parsing row {}", row_right), position))
|
||||
return Err(self.set_error(
|
||||
&format!("Failed parsing row {}", row_right),
|
||||
position,
|
||||
));
|
||||
}
|
||||
};
|
||||
if row_left > LAST_ROW {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
stringify::{stringify_reference, DisplaceData},
|
||||
Node, Reference,
|
||||
stringify::{DisplaceData, stringify_reference},
|
||||
};
|
||||
use crate::{
|
||||
constants::{LAST_COLUMN, LAST_ROW},
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::lexer::LexerMode;
|
||||
use crate::expressions::parser::stringify::{
|
||||
to_rc_format, to_string, to_string_displaced, DisplaceData,
|
||||
DisplaceData, to_rc_format, to_string, to_string_displaced,
|
||||
};
|
||||
use crate::expressions::parser::{Node, Parser};
|
||||
use crate::expressions::types::CellReferenceRC;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::parser::stringify::to_string;
|
||||
use crate::expressions::parser::Parser;
|
||||
use crate::expressions::parser::stringify::to_string;
|
||||
use crate::expressions::types::CellReferenceRC;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::parser::move_formula::{move_formula, MoveContext};
|
||||
use crate::expressions::parser::Parser;
|
||||
use crate::expressions::parser::move_formula::{MoveContext, move_formula};
|
||||
use crate::expressions::types::{Area, CellReferenceRC};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::lexer::LexerMode;
|
||||
|
||||
use crate::expressions::parser::stringify::{to_rc_format, to_string};
|
||||
use crate::expressions::parser::Parser;
|
||||
use crate::expressions::parser::stringify::{to_rc_format, to_string};
|
||||
use crate::expressions::types::CellReferenceRC;
|
||||
|
||||
struct Formula<'a> {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::parser::stringify::to_string;
|
||||
use crate::expressions::parser::Parser;
|
||||
use crate::expressions::parser::stringify::to_string;
|
||||
use crate::expressions::types::CellReferenceRC;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{move_formula::ref_is_in_area, Node};
|
||||
use super::{Node, move_formula::ref_is_in_area};
|
||||
|
||||
use crate::expressions::types::{Area, CellReferenceIndex};
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ pub fn format_number(value_original: f64, format: &str, locale: &Locale) -> Form
|
||||
text: "#VALUE!".to_owned(),
|
||||
color: None,
|
||||
error: Some(e),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
for token in tokens {
|
||||
@@ -391,11 +391,7 @@ pub fn format_number(value_original: f64, format: &str, locale: &Locale) -> Form
|
||||
if l_exp <= p.exponent_digit_count {
|
||||
if !(number_index < 0 && digit.kind == '#') {
|
||||
let c = if number_index < 0 {
|
||||
if digit.kind == '?' {
|
||||
' '
|
||||
} else {
|
||||
'0'
|
||||
}
|
||||
if digit.kind == '?' { ' ' } else { '0' }
|
||||
} else {
|
||||
exponent_part[number_index as usize]
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
formatter::format::format_number,
|
||||
locale::{get_locale, Locale},
|
||||
locale::{Locale, get_locale},
|
||||
};
|
||||
|
||||
fn get_default_locale() -> &'static Locale {
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Model {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Out of range parameters for date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
let day = date.day() as f64;
|
||||
@@ -54,7 +54,7 @@ impl Model {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Out of range parameters for date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
let month = date.month() as f64;
|
||||
@@ -87,7 +87,7 @@ impl Model {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Out of range parameters for date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
if serial_number > MAXIMUM_DATE_SERIAL_NUMBER as i64 {
|
||||
@@ -192,7 +192,7 @@ impl Model {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Out of range parameters for date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
let year = date.year() as f64;
|
||||
@@ -216,7 +216,7 @@ impl Model {
|
||||
error: Error::NUM,
|
||||
origin: cell,
|
||||
message: "Out of range parameters for date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -266,7 +266,7 @@ impl Model {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Invalid date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
// 693_594 is computed as:
|
||||
@@ -296,7 +296,7 @@ impl Model {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Invalid date".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
// 693_594 is computed as:
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
use std::f64::consts::FRAC_2_PI;
|
||||
|
||||
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
|
||||
use super::bessel_util::{FRAC_2_SQRT_PI, HUGE, high_word, split_words};
|
||||
|
||||
// R0/S0 on [0, 2.00]
|
||||
const R02: f64 = 1.562_499_999_999_999_5e-2; // 0x3F8FFFFF, 0xFFFFFFFD
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
use std::f64::consts::FRAC_2_PI;
|
||||
|
||||
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
|
||||
use super::bessel_util::{FRAC_2_SQRT_PI, HUGE, high_word, split_words};
|
||||
|
||||
// R0/S0 on [0,2]
|
||||
const R00: f64 = -6.25e-2; // 0xBFB00000, 0x00000000
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
use super::{
|
||||
bessel_j0_y0::{j0, y0},
|
||||
bessel_j1_y1::{j1, y1},
|
||||
bessel_util::{split_words, FRAC_2_SQRT_PI},
|
||||
bessel_util::{FRAC_2_SQRT_PI, split_words},
|
||||
};
|
||||
|
||||
// Special cases are:
|
||||
@@ -232,11 +232,7 @@ pub(crate) fn jn(n: i32, x: f64) -> f64 {
|
||||
}
|
||||
}
|
||||
};
|
||||
if sign == 1 {
|
||||
-b
|
||||
} else {
|
||||
b
|
||||
}
|
||||
if sign == 1 { -b } else { b }
|
||||
}
|
||||
|
||||
// Yn returns the order-n Bessel function of the second kind.
|
||||
@@ -321,9 +317,5 @@ pub(crate) fn yn(n: i32, x: f64) -> f64 {
|
||||
}
|
||||
b
|
||||
};
|
||||
if sign > 0 {
|
||||
b
|
||||
} else {
|
||||
-b
|
||||
}
|
||||
if sign > 0 { b } else { -b }
|
||||
}
|
||||
|
||||
@@ -45,9 +45,5 @@ pub(crate) fn erf(x: f64) -> f64 {
|
||||
}
|
||||
|
||||
let res = t * f64::exp(-x_abs * x_abs + 0.5 * (cof[0] + ty * d) - dd);
|
||||
if x < 0.0 {
|
||||
res - 1.0
|
||||
} else {
|
||||
1.0 - res
|
||||
}
|
||||
if x < 0.0 { res - 1.0 } else { 1.0 - res }
|
||||
}
|
||||
|
||||
@@ -698,7 +698,7 @@ impl Model {
|
||||
error: error.0,
|
||||
origin: cell,
|
||||
message: error.1,
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
CalcResult::Number(ipmt)
|
||||
@@ -762,7 +762,7 @@ impl Model {
|
||||
error: error.0,
|
||||
origin: cell,
|
||||
message: error.1,
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
CalcResult::Number(ppmt)
|
||||
@@ -1075,7 +1075,7 @@ impl Model {
|
||||
error,
|
||||
origin: cell,
|
||||
message,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1096,7 +1096,7 @@ impl Model {
|
||||
error,
|
||||
origin: cell,
|
||||
message,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1634,7 +1634,7 @@ impl Model {
|
||||
error: error.0,
|
||||
origin: cell,
|
||||
message: error.1,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1702,7 +1702,7 @@ impl Model {
|
||||
error: error.0,
|
||||
origin: cell,
|
||||
message: error.1,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1750,11 +1750,7 @@ impl Model {
|
||||
rate = 1.0
|
||||
};
|
||||
let value = if rate == 1.0 {
|
||||
if period == 1.0 {
|
||||
cost
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
if period == 1.0 { cost } else { 0.0 }
|
||||
} else {
|
||||
cost * (1.0 - rate).powf(period - 1.0)
|
||||
};
|
||||
|
||||
@@ -257,10 +257,10 @@ impl Model {
|
||||
{
|
||||
match defined_name {
|
||||
ParsedDefinedName::CellReference(reference) => {
|
||||
return CalcResult::Number(reference.sheet as f64 + 1.0)
|
||||
return CalcResult::Number(reference.sheet as f64 + 1.0);
|
||||
}
|
||||
ParsedDefinedName::RangeReference(range) => {
|
||||
return CalcResult::Number(range.left.sheet as f64 + 1.0)
|
||||
return CalcResult::Number(range.left.sheet as f64 + 1.0);
|
||||
}
|
||||
ParsedDefinedName::InvalidDefinedNameFormula => {
|
||||
return CalcResult::Error {
|
||||
@@ -296,7 +296,7 @@ impl Model {
|
||||
error: Error::NAME,
|
||||
origin: cell,
|
||||
message: format!("Name not found: {name}"),
|
||||
}
|
||||
};
|
||||
}
|
||||
arg => {
|
||||
// Now it should be the name of a sheet
|
||||
|
||||
@@ -388,7 +388,7 @@ impl Model {
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", first_range.left.sheet),
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
let max_row = dimension.max_row;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
calc_result::CalcResult,
|
||||
expressions::{
|
||||
parser::{parse_range, Node},
|
||||
parser::{Node, parse_range},
|
||||
token::Error,
|
||||
types::CellReferenceIndex,
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
text_util::{substitute, text_after, text_before, Case},
|
||||
text_util::{Case, substitute, text_after, text_before},
|
||||
util::from_wildcard_to_regex,
|
||||
};
|
||||
|
||||
@@ -368,7 +368,7 @@ impl Model {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Empty cell".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -629,7 +629,7 @@ impl Model {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Expecting number".to_string(),
|
||||
}
|
||||
};
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
CalcResult::Range { .. } => {
|
||||
|
||||
@@ -57,8 +57,8 @@ mod test;
|
||||
#[cfg(test)]
|
||||
pub mod mock_time;
|
||||
|
||||
pub use model::get_milliseconds_since_epoch;
|
||||
pub use model::Model;
|
||||
pub use model::get_milliseconds_since_epoch;
|
||||
pub use user_model::BorderArea;
|
||||
pub use user_model::ClipboardData;
|
||||
pub use user_model::UserModel;
|
||||
|
||||
@@ -10,11 +10,11 @@ use crate::{
|
||||
expressions::{
|
||||
lexer::LexerMode,
|
||||
parser::{
|
||||
move_formula::{move_formula, MoveContext},
|
||||
stringify::{rename_defined_name_in_node, to_rc_format, to_string},
|
||||
Node, Parser,
|
||||
move_formula::{MoveContext, move_formula},
|
||||
stringify::{rename_defined_name_in_node, to_rc_format, to_string},
|
||||
},
|
||||
token::{get_error_by_name, Error, OpCompare, OpProduct, OpSum, OpUnary},
|
||||
token::{Error, OpCompare, OpProduct, OpSum, OpUnary, get_error_by_name},
|
||||
types::*,
|
||||
utils::{self, is_valid_column_number, is_valid_identifier, is_valid_row},
|
||||
},
|
||||
@@ -24,8 +24,8 @@ use crate::{
|
||||
},
|
||||
functions::util::compare_values,
|
||||
implicit_intersection::implicit_intersection,
|
||||
language::{get_language, Language},
|
||||
locale::{get_locale, Currency, Locale},
|
||||
language::{Language, get_language},
|
||||
locale::{Currency, Locale, get_locale},
|
||||
types::*,
|
||||
utils as common,
|
||||
};
|
||||
@@ -1872,12 +1872,29 @@ impl Model {
|
||||
}
|
||||
|
||||
/// Returns the style for cell (`sheet`, `row`, `column`)
|
||||
/// If the cell does not have a style defined we check the row, otherwise the column and finally a default
|
||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
||||
let style = self.workbook.styles.get_style(style_index)?;
|
||||
Ok(style)
|
||||
}
|
||||
|
||||
/// Returns the style defined in a cell if any.
|
||||
pub fn get_cell_style_or_none(
|
||||
&self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<Option<Style>, String> {
|
||||
let style = self
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.cell(row, column)
|
||||
.map(|c| self.workbook.styles.get_style(c.get_style()))
|
||||
.transpose();
|
||||
style
|
||||
}
|
||||
|
||||
/// Returns an internal binary representation of the workbook
|
||||
///
|
||||
/// See also:
|
||||
@@ -2161,6 +2178,73 @@ impl Model {
|
||||
Err("Defined name not found".to_string())
|
||||
}
|
||||
}
|
||||
/// Returns the style object of a column, if any
|
||||
pub fn get_column_style(&self, sheet: u32, column: i32) -> Result<Option<Style>, String> {
|
||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||
let cols = &worksheet.cols;
|
||||
for col in cols {
|
||||
if column >= col.min && column <= col.max {
|
||||
if let Some(style_index) = col.style {
|
||||
let style = self.workbook.styles.get_style(style_index)?;
|
||||
return Ok(Some(style));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
Err("Invalid sheet".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style object of a row, if any
|
||||
pub fn get_row_style(&self, sheet: u32, row: i32) -> Result<Option<Style>, String> {
|
||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||
let rows = &worksheet.rows;
|
||||
for r in rows {
|
||||
if row == r.r {
|
||||
let style = self.workbook.styles.get_style(r.s)?;
|
||||
return Ok(Some(style));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
Err("Invalid sheet".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a column with style
|
||||
pub fn set_column_style(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
column: i32,
|
||||
style: &Style,
|
||||
) -> Result<(), String> {
|
||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_column_style(column, style_index)
|
||||
}
|
||||
|
||||
/// Sets a row with style
|
||||
pub fn set_row_style(&mut self, sheet: u32, row: i32, style: &Style) -> Result<(), String> {
|
||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_row_style(row, style_index)
|
||||
}
|
||||
|
||||
/// Deletes the style of a column if the is any
|
||||
pub fn delete_column_style(&mut self, sheet: u32, column: i32) -> Result<(), String> {
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.delete_column_style(column)
|
||||
}
|
||||
|
||||
/// Deletes the style of a row if there is any
|
||||
pub fn delete_row_style(&mut self, sheet: u32, row: i32) -> Result<(), String> {
|
||||
self.workbook.worksheet_mut(sheet)?.delete_row_style(row)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,14 +8,14 @@ use crate::{
|
||||
expressions::{
|
||||
lexer::LexerMode,
|
||||
parser::{
|
||||
stringify::{rename_sheet_in_node, to_rc_format},
|
||||
Parser,
|
||||
stringify::{rename_sheet_in_node, to_rc_format},
|
||||
},
|
||||
types::CellReferenceRC,
|
||||
},
|
||||
language::get_language,
|
||||
locale::get_locale,
|
||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||
model::{Model, ParsedDefinedName, get_milliseconds_since_epoch},
|
||||
types::{
|
||||
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
||||
},
|
||||
@@ -301,7 +301,7 @@ impl Model {
|
||||
};
|
||||
if sheet_index >= sheet_count {
|
||||
return Err("Sheet index too large".to_string());
|
||||
}
|
||||
};
|
||||
self.workbook.worksheets.remove(sheet_index as usize);
|
||||
self.reset_parsed_structures();
|
||||
Ok(())
|
||||
|
||||
@@ -150,7 +150,7 @@ pub fn format_number(value: f64, format_code: &str, locale: &str) -> Formatted {
|
||||
text: "#ERROR!".to_owned(),
|
||||
color: None,
|
||||
error: Some("Invalid locale".to_string()),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
formatter::format::format_number(value, format_code, locale)
|
||||
|
||||
@@ -4,8 +4,6 @@ use crate::{
|
||||
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles},
|
||||
};
|
||||
|
||||
// TODO: Move Styles and all related types from crate::types here
|
||||
// Not doing it right now to not have conflicts with exporter branch
|
||||
impl Styles {
|
||||
fn get_font_index(&self, font: &Font) -> Option<i32> {
|
||||
for (font_index, item) in self.fonts.iter().enumerate() {
|
||||
|
||||
@@ -37,6 +37,7 @@ mod test_model_cell_clear_all;
|
||||
mod test_model_is_empty_cell;
|
||||
mod test_move_formula;
|
||||
mod test_quote_prefix;
|
||||
mod test_row_column_styles;
|
||||
mod test_set_user_input;
|
||||
mod test_sheet_markup;
|
||||
mod test_sheets;
|
||||
|
||||
@@ -206,9 +206,11 @@ fn test_delete_column_width() {
|
||||
let (sheet, column) = (0, 5);
|
||||
let normal_width = model.get_column_width(sheet, column).unwrap();
|
||||
// Set the width of one column to 5 times the normal width
|
||||
assert!(model
|
||||
.set_column_width(sheet, column, normal_width * 5.0)
|
||||
.is_ok());
|
||||
assert!(
|
||||
model
|
||||
.set_column_width(sheet, column, normal_width * 5.0)
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
// delete it
|
||||
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
||||
|
||||
@@ -179,52 +179,60 @@ fn test_move_formula_rectangle() {
|
||||
width: 2,
|
||||
height: 20,
|
||||
};
|
||||
assert!(model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 3,
|
||||
row: 1,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_err());
|
||||
assert!(model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 2,
|
||||
row: 1,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_ok());
|
||||
assert!(model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 1,
|
||||
row: 20,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_ok());
|
||||
assert!(model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 1,
|
||||
row: 21,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 3,
|
||||
row: 1,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 2,
|
||||
row: 1,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 1,
|
||||
row: 20,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
model
|
||||
.move_cell_value_to_area(
|
||||
value,
|
||||
&CellReferenceIndex {
|
||||
sheet: 0,
|
||||
column: 1,
|
||||
row: 21,
|
||||
},
|
||||
target,
|
||||
area
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
32
base/src/test/test_row_column_styles.rs
Normal file
32
base/src/test/test_row_column_styles.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{constants::DEFAULT_COLUMN_WIDTH, test::util::new_empty_model};
|
||||
|
||||
#[test]
|
||||
fn test_model_set_cells_with_values_styles() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
let style_base = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
let mut style = style_base.clone();
|
||||
style.font.b = true;
|
||||
|
||||
model.set_column_style(0, 10, &style).unwrap();
|
||||
|
||||
assert!(model.get_style_for_cell(0, 21, 10).unwrap().font.b);
|
||||
|
||||
model.delete_column_style(0, 10).unwrap();
|
||||
|
||||
// There are no styles in the column
|
||||
assert!(model.workbook.worksheets[0].cols.is_empty());
|
||||
|
||||
// lets change the column width and check it does not affect the style
|
||||
model
|
||||
.set_column_width(0, 10, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
model.set_column_style(0, 10, &style).unwrap();
|
||||
|
||||
model.delete_column_style(0, 10).unwrap();
|
||||
|
||||
// There are no styles in the column
|
||||
assert!(model.workbook.worksheets[0].cols.len() == 1);
|
||||
}
|
||||
@@ -3,7 +3,9 @@ mod test_autofill_columns;
|
||||
mod test_autofill_rows;
|
||||
mod test_border;
|
||||
mod test_clear_cells;
|
||||
mod test_column_style;
|
||||
mod test_defined_names;
|
||||
mod test_delete_row_column_formatting;
|
||||
mod test_diff_queue;
|
||||
mod test_evaluation;
|
||||
mod test_general;
|
||||
@@ -13,9 +15,11 @@ mod test_on_area_selection;
|
||||
mod test_on_expand_selected_range;
|
||||
mod test_on_paste_styles;
|
||||
mod test_paste_csv;
|
||||
mod test_recursive;
|
||||
mod test_rename_sheet;
|
||||
mod test_row_column;
|
||||
mod test_sheet_state;
|
||||
mod test_sheets_undo_redo;
|
||||
mod test_styles;
|
||||
mod test_to_from_bytes;
|
||||
mod test_undo_redo;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{constants::DEFAULT_COLUMN_WIDTH, UserModel};
|
||||
use crate::{UserModel, constants::DEFAULT_COLUMN_WIDTH};
|
||||
|
||||
#[test]
|
||||
fn add_undo_redo() {
|
||||
@@ -9,7 +9,7 @@ fn add_undo_redo() {
|
||||
model.set_user_input(1, 1, 1, "=1 + 1").unwrap();
|
||||
model.set_user_input(1, 1, 2, "=A1*3").unwrap();
|
||||
model
|
||||
.set_column_width(1, 5, 5.0 * DEFAULT_COLUMN_WIDTH)
|
||||
.set_columns_width(1, 5, 5, 5.0 * DEFAULT_COLUMN_WIDTH)
|
||||
.unwrap();
|
||||
model.new_sheet().unwrap();
|
||||
model.set_user_input(2, 1, 1, "=Sheet2!B1").unwrap();
|
||||
@@ -25,9 +25,6 @@ fn add_undo_redo() {
|
||||
assert_eq!(model.get_formatted_cell_value(2, 1, 1), Ok("6".to_string()));
|
||||
|
||||
model.delete_sheet(1).unwrap();
|
||||
|
||||
assert!(!model.can_undo());
|
||||
assert!(!model.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||
use crate::expressions::types::Area;
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::UserModel;
|
||||
|
||||
#[test]
|
||||
fn basic_tests() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||
use crate::expressions::types::Area;
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::UserModel;
|
||||
|
||||
#[test]
|
||||
fn basic_tests() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
BorderArea, UserModel,
|
||||
constants::{LAST_COLUMN, LAST_ROW},
|
||||
expressions::{types::Area, utils::number_to_column},
|
||||
types::{Border, BorderItem, BorderStyle},
|
||||
BorderArea, UserModel,
|
||||
};
|
||||
|
||||
// checks there are no borders in the sheet
|
||||
@@ -520,14 +520,19 @@ fn borders_top() {
|
||||
.unwrap();
|
||||
model.set_area_with_border(range, &border_area).unwrap();
|
||||
check_borders(&model);
|
||||
for row in 5..9 {
|
||||
for row in 4..9 {
|
||||
for column in 6..9 {
|
||||
let style = model.get_cell_style(0, row, column).unwrap();
|
||||
let border_item = BorderItem {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#FF5566".to_string()),
|
||||
};
|
||||
let bottom = if row == 8 {
|
||||
let bottom = if row != 4 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
};
|
||||
let top = if row != 5 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
@@ -537,7 +542,7 @@ fn borders_top() {
|
||||
diagonal_down: false,
|
||||
left: None,
|
||||
right: None,
|
||||
top: Some(border_item.clone()),
|
||||
top,
|
||||
bottom,
|
||||
diagonal: None,
|
||||
};
|
||||
@@ -647,12 +652,12 @@ fn borders_right() {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#FF5566".to_string()),
|
||||
};
|
||||
let left = if column == 6 {
|
||||
let left = if column != 9 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
};
|
||||
let right = if column == 9 {
|
||||
let right = if column != 8 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
@@ -705,7 +710,7 @@ fn borders_bottom() {
|
||||
color: Some("#FF5566".to_string()),
|
||||
};
|
||||
// The top will also have a value for all but the first one
|
||||
let top = if row == 5 {
|
||||
let bottom = if row != 8 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
@@ -715,8 +720,8 @@ fn borders_bottom() {
|
||||
diagonal_down: false,
|
||||
left: None,
|
||||
right: None,
|
||||
top,
|
||||
bottom: Some(border_item.clone()),
|
||||
top: None,
|
||||
bottom,
|
||||
diagonal: None,
|
||||
};
|
||||
assert_eq!(style.border, expected_border);
|
||||
@@ -751,18 +756,13 @@ fn borders_left() {
|
||||
model.set_area_with_border(range, &border_area).unwrap();
|
||||
|
||||
for row in 5..9 {
|
||||
for column in 5..9 {
|
||||
for column in 6..9 {
|
||||
let style = model.get_cell_style(0, row, column).unwrap();
|
||||
let border_item = BorderItem {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#FF5566".to_string()),
|
||||
};
|
||||
let left = if column == 5 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
};
|
||||
let right = if column == 8 {
|
||||
let left = if column != 6 {
|
||||
None
|
||||
} else {
|
||||
Some(border_item.clone())
|
||||
@@ -771,13 +771,29 @@ fn borders_left() {
|
||||
diagonal_up: false,
|
||||
diagonal_down: false,
|
||||
left,
|
||||
right,
|
||||
right: None,
|
||||
top: None,
|
||||
bottom: None,
|
||||
diagonal: None,
|
||||
};
|
||||
assert_eq!(style.border, expected_border);
|
||||
}
|
||||
// Column 5 has a border to the right, of course:
|
||||
let style = model.get_cell_style(0, row, 5).unwrap();
|
||||
let border_item = BorderItem {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#FF5566".to_string()),
|
||||
};
|
||||
let expected_border = Border {
|
||||
diagonal_up: false,
|
||||
diagonal_down: false,
|
||||
left: None,
|
||||
right: Some(border_item.clone()),
|
||||
top: None,
|
||||
bottom: None,
|
||||
diagonal: None,
|
||||
};
|
||||
assert_eq!(style.border, expected_border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,10 +1034,7 @@ fn border_top() {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#F2F2F2".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
model._get_cell_actual_border("C4").bottom,
|
||||
Some(border_item)
|
||||
);
|
||||
assert_eq!(model._get_cell_actual_border("C4").top, Some(border_item));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{expressions::types::Area, UserModel};
|
||||
use crate::{UserModel, expressions::types::Area};
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
|
||||
504
base/src/test/user_model/test_column_style.rs
Normal file
504
base/src/test/user_model/test_column_style.rs
Normal file
@@ -0,0 +1,504 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW};
|
||||
use crate::expressions::types::Area;
|
||||
|
||||
#[test]
|
||||
fn column_width() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(!style.font.i);
|
||||
assert!(!style.font.b);
|
||||
assert!(!style.font.u);
|
||||
assert!(!style.font.strike);
|
||||
assert_eq!(style.font.color, Some("#000000".to_owned()));
|
||||
|
||||
// Set the whole column style and check it works
|
||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
|
||||
// undo and check it works
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(!style.font.b);
|
||||
|
||||
// redo and check it works
|
||||
model.redo().unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
|
||||
// change the column width and check it does not affect the style
|
||||
model
|
||||
.set_columns_width(0, 7, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
let style = model.get_cell_style(0, 109, 7).unwrap();
|
||||
assert!(style.font.b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_style() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let cell_g123 = Area {
|
||||
sheet: 0,
|
||||
row: 123,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
// Set G123 background to red
|
||||
model
|
||||
.update_range_style(&cell_g123, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// Now set the style of the whole column
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Get the style of G123
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check the style of G123 is now what it was before
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
model.redo().unwrap();
|
||||
|
||||
// Check G123 has the column style now
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_column() {
|
||||
// We set the row style, then a column style
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Check G3 has the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
// undo twice. Color must be default
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_row() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let default_style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// Check G3 has the row style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check G3 has the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check G3 has the default_style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, default_style.fill.bg_color);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_column_column() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_c_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 3,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let column_e_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 5,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_5_range = Area {
|
||||
sheet: 0,
|
||||
row: 5,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_5_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_c_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&column_e_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
|
||||
// Test E5 has the default style
|
||||
let style = model.get_cell_style(0, 5, 5).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_column_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
model
|
||||
.set_columns_width(0, 7, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
DEFAULT_COLUMN_WIDTH * 2.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn height_row_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model
|
||||
.set_rows_height(0, 10, 10, DEFAULT_ROW_HEIGHT * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let row_10_range = Area {
|
||||
sheet: 0,
|
||||
row: 10,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
model
|
||||
.update_range_style(&row_10_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_row_height(0, 10).unwrap(),
|
||||
2.0 * DEFAULT_ROW_HEIGHT
|
||||
);
|
||||
model.undo().unwrap();
|
||||
assert_eq!(
|
||||
model.get_row_height(0, 10).unwrap(),
|
||||
2.0 * DEFAULT_ROW_HEIGHT
|
||||
);
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_row_height(0, 10).unwrap(), DEFAULT_ROW_HEIGHT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_row_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let cell_g12 = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let row_12_range = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// Set G12 background to red
|
||||
model
|
||||
.update_range_style(&cell_g12, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&row_12_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#CCC111".to_string()));
|
||||
model.undo().unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_column_style_then_cell() {
|
||||
// We check that if we set a cell style in a column that already has a style
|
||||
// the styles compound
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let cell_g12 = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
// Set G12 background to red
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&cell_g12, "alignment.horizontal", "center")
|
||||
.unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_string()));
|
||||
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_row_style_then_cell() {
|
||||
// We check that if we set a cell style in a column that already has a style
|
||||
// the styles compound
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let cell_g12 = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let row_12_range = Area {
|
||||
sheet: 0,
|
||||
row: 12,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// Set G12 background to red
|
||||
model
|
||||
.update_range_style(&row_12_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&cell_g12, "alignment.horizontal", "center")
|
||||
.unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 12, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_style_then_row_alignment() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
model
|
||||
.update_range_style(&row_3_range, "alignment.horizontal", "center")
|
||||
.unwrap();
|
||||
// check the row alignment does not affect the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_style_then_width() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
model
|
||||
.set_columns_width(0, 7, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
|
||||
// Check column width worked:
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
DEFAULT_COLUMN_WIDTH * 2.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_row_column_column() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_c_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 3,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let column_e_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 5,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_5_range = Area {
|
||||
sheet: 0,
|
||||
row: 5,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// update the row style
|
||||
model
|
||||
.update_range_style(&row_5_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// update the column style
|
||||
model
|
||||
.update_range_style(&column_c_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&column_e_range, "fill.bg_color", "#CCC111")
|
||||
.unwrap();
|
||||
|
||||
// test E5 has the column style
|
||||
let style = model.get_cell_style(0, 5, 5).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#CCC111".to_string()));
|
||||
}
|
||||
@@ -396,3 +396,30 @@ fn undo_redo() {
|
||||
Ok("Hola!".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_scope_to_first_sheet() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.new_sheet().unwrap();
|
||||
model.set_user_input(0, 1, 1, "Hello").unwrap();
|
||||
model
|
||||
.set_user_input(1, 2, 1, r#"=CONCATENATE(MyName, " world!")"#)
|
||||
.unwrap();
|
||||
model
|
||||
.new_defined_name("myName", None, "Sheet1!$A$1")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_formatted_cell_value(1, 2, 1),
|
||||
Ok("Hello world!".to_string())
|
||||
);
|
||||
|
||||
model
|
||||
.update_defined_name("myName", None, "myName", Some(0), "Sheet1!$A$1")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_formatted_cell_value(1, 2, 1),
|
||||
Ok("#NAME?".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
243
base/src/test/user_model/test_delete_row_column_formatting.rs
Normal file
243
base/src/test/user_model/test_delete_row_column_formatting.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW},
|
||||
expressions::types::Area,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn delete_column_formatting() {
|
||||
// We are going to delete formatting in column G (7)
|
||||
// There are cells with their own styles
|
||||
// There are rows with their own styles
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let cell_g123 = Area {
|
||||
sheet: 0,
|
||||
row: 123,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// Set the style of the whole column
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Set G123 background to red
|
||||
model
|
||||
.update_range_style(&cell_g123, "fill.bg_color", "#FF5533")
|
||||
.unwrap();
|
||||
|
||||
// Set the style of the whole row
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
|
||||
// Delete the column formatting
|
||||
model.range_clear_formatting(&column_g_range).unwrap();
|
||||
|
||||
// Check the style of G123 is now what it was before
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
// Check the style of the whole row is still there
|
||||
let style = model.get_cell_style(0, 3, 1).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
// Check the style of the whole column is now gone
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
let style = model.get_cell_style(0, 40, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// Check the style of G123 is now what it was before
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#FF5533".to_owned()));
|
||||
|
||||
// Check G3 is the row style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
// Check G40 is the column style
|
||||
let style = model.get_cell_style(0, 40, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_owned()));
|
||||
|
||||
model.redo().unwrap();
|
||||
|
||||
// Check the style of G123 is now what it was before
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
// Check the style of the whole row is still there
|
||||
let style = model.get_cell_style(0, 3, 1).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#333444".to_owned()));
|
||||
|
||||
// Check the style of the whole column is now gone
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
let style = model.get_cell_style(0, 40, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_width() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model
|
||||
.set_columns_width(0, 7, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
// Set the style of the whole column
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
// Delete the column formatting
|
||||
model.range_clear_formatting(&column_g_range).unwrap();
|
||||
// This does not change the column width
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
2.0 * DEFAULT_COLUMN_WIDTH
|
||||
);
|
||||
|
||||
model.undo().unwrap();
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
2.0 * DEFAULT_COLUMN_WIDTH
|
||||
);
|
||||
model.redo().unwrap();
|
||||
assert_eq!(
|
||||
model.get_column_width(0, 7).unwrap(),
|
||||
2.0 * DEFAULT_COLUMN_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_row_style_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model
|
||||
.set_columns_width(0, 7, 7, DEFAULT_COLUMN_WIDTH * 2.0)
|
||||
.unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_123_range = Area {
|
||||
sheet: 0,
|
||||
row: 123,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let delete_range = Area {
|
||||
sheet: 0,
|
||||
row: 120,
|
||||
column: 5,
|
||||
width: 20,
|
||||
height: 20,
|
||||
};
|
||||
|
||||
// Set the style of the whole column
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&row_123_range, "fill.bg_color", "#111222")
|
||||
.unwrap();
|
||||
|
||||
model.range_clear_formatting(&delete_range).unwrap();
|
||||
|
||||
// check G123 is empty
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
|
||||
// uno clear formatting
|
||||
model.undo().unwrap();
|
||||
|
||||
// G123 has the row style
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#111222".to_owned()));
|
||||
|
||||
// undo twice
|
||||
model.undo().unwrap();
|
||||
model.undo().unwrap();
|
||||
|
||||
// check G123 is empty
|
||||
let style = model.get_cell_style(0, 123, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_row_row_height_undo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
let row_3_range = Area {
|
||||
sheet: 0,
|
||||
row: 3,
|
||||
column: 1,
|
||||
width: LAST_COLUMN,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#555666")
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.set_rows_height(0, 3, 3, DEFAULT_ROW_HEIGHT * 2.0)
|
||||
.unwrap();
|
||||
|
||||
model
|
||||
.update_range_style(&row_3_range, "fill.bg_color", "#111222")
|
||||
.unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
// check G3 has the column style
|
||||
let style = model.get_cell_style(0, 3, 7).unwrap();
|
||||
assert_eq!(style.fill.bg_color, Some("#555666".to_string()));
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn send_queue() {
|
||||
let mut model1 = UserModel::from_model(new_empty_model());
|
||||
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||
model1.set_column_width(0, 3, width).unwrap();
|
||||
model1.set_columns_width(0, 3, 3, width).unwrap();
|
||||
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||
let send_queue = model1.flush_send_queue();
|
||||
|
||||
@@ -34,7 +34,7 @@ fn apply_external_diffs_wrong_str() {
|
||||
fn queue_undo_redo() {
|
||||
let mut model1 = UserModel::from_model(new_empty_model());
|
||||
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||
model1.set_column_width(0, 3, width).unwrap();
|
||||
model1.set_columns_width(0, 3, 3, width).unwrap();
|
||||
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||
assert!(model1.undo().is_ok());
|
||||
assert!(model1.redo().is_ok());
|
||||
@@ -57,8 +57,8 @@ fn queue_undo_redo_multiple() {
|
||||
// do a bunch of things
|
||||
model1.set_frozen_columns_count(0, 5).unwrap();
|
||||
model1.set_frozen_rows_count(0, 6).unwrap();
|
||||
model1.set_column_width(0, 7, 300.0).unwrap();
|
||||
model1.set_row_height(0, 23, 123.0).unwrap();
|
||||
model1.set_columns_width(0, 7, 7, 300.0).unwrap();
|
||||
model1.set_rows_height(0, 23, 23, 123.0).unwrap();
|
||||
model1.set_user_input(0, 55, 55, "=42+8").unwrap();
|
||||
|
||||
for row in 1..5 {
|
||||
@@ -157,7 +157,9 @@ fn new_sheet() {
|
||||
#[test]
|
||||
fn wrong_diffs_handled() {
|
||||
let mut model = UserModel::from_model(new_empty_model());
|
||||
assert!(model
|
||||
.apply_external_diffs("Hello world".as_bytes())
|
||||
.is_err());
|
||||
assert!(
|
||||
model
|
||||
.apply_external_diffs("Hello world".as_bytes())
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::types::CellType;
|
||||
use crate::UserModel;
|
||||
|
||||
#[test]
|
||||
fn set_user_input_errors() {
|
||||
@@ -59,7 +59,7 @@ fn insert_remove_rows() {
|
||||
// Insert some data in row 5 (and change the style)
|
||||
assert!(model.set_user_input(0, 5, 1, "100$").is_ok());
|
||||
// Change the height of the column
|
||||
assert!(model.set_row_height(0, 5, 3.0 * height).is_ok());
|
||||
assert!(model.set_rows_height(0, 5, 5, 3.0 * height).is_ok());
|
||||
|
||||
// remove the row
|
||||
assert!(model.delete_row(0, 5).is_ok());
|
||||
@@ -95,7 +95,7 @@ fn insert_remove_columns() {
|
||||
// Insert some data in row 5 (and change the style) in E1
|
||||
assert!(model.set_user_input(0, 1, 5, "100$").is_ok());
|
||||
// Change the width of the column
|
||||
assert!(model.set_column_width(0, 5, 3.0 * column_width).is_ok());
|
||||
assert!(model.set_columns_width(0, 5, 5, 3.0 * column_width).is_ok());
|
||||
assert_eq!(model.get_column_width(0, 5).unwrap(), 3.0 * column_width);
|
||||
|
||||
// remove the column
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::UserModel;
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn basic_tests() {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{
|
||||
DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH,
|
||||
LAST_COLUMN,
|
||||
},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_WINDOW_WIDTH},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_WINDOW_WIDTH, LAST_COLUMN},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::types::Fill;
|
||||
use crate::UserModel;
|
||||
|
||||
#[test]
|
||||
fn simple_pasting() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{expressions::types::Area, UserModel};
|
||||
use crate::{UserModel, expressions::types::Area};
|
||||
|
||||
#[test]
|
||||
fn csv_paste() {
|
||||
|
||||
42
base/src/test/user_model/test_recursive.rs
Normal file
42
base/src/test/user_model/test_recursive.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel, constants::LAST_ROW, expressions::types::Area, test::util::new_empty_model,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn two_columns() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
|
||||
// Set style in column C (column 3)
|
||||
let column_c_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 3,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
model
|
||||
.update_range_style(&column_c_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
model.set_user_input(0, 5, 3, "2").unwrap();
|
||||
|
||||
// Set Style in column G (column 7)
|
||||
let column_g_range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 7,
|
||||
width: 1,
|
||||
height: LAST_ROW,
|
||||
};
|
||||
|
||||
model
|
||||
.update_range_style(&column_g_range, "fill.bg_color", "#333444")
|
||||
.unwrap();
|
||||
model.set_user_input(0, 5, 6, "42").unwrap();
|
||||
// Set formula in G5: =F5*C5
|
||||
model.set_user_input(0, 5, 7, "=F5*C5").unwrap();
|
||||
|
||||
assert_eq!(model.get_formatted_cell_value(0, 5, 7).unwrap(), "84");
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -59,7 +59,7 @@ fn simple_delete_column() {
|
||||
model.set_user_input(0, 1, 5, "3").unwrap();
|
||||
model.set_user_input(0, 2, 5, "=E1*2").unwrap();
|
||||
model
|
||||
.set_column_width(0, 5, DEFAULT_COLUMN_WIDTH * 3.0)
|
||||
.set_columns_width(0, 5, 5, DEFAULT_COLUMN_WIDTH * 3.0)
|
||||
.unwrap();
|
||||
|
||||
model.delete_column(0, 5).unwrap();
|
||||
@@ -116,7 +116,7 @@ fn simple_delete_row() {
|
||||
model.set_user_input(0, 15, 6, "=D15*2").unwrap();
|
||||
|
||||
model
|
||||
.set_row_height(0, 15, DEFAULT_ROW_HEIGHT * 3.0)
|
||||
.set_rows_height(0, 15, 15, DEFAULT_ROW_HEIGHT * 3.0)
|
||||
.unwrap();
|
||||
|
||||
model.delete_row(0, 15).unwrap();
|
||||
@@ -172,3 +172,42 @@ fn row_heigh_increases_automatically() {
|
||||
.unwrap();
|
||||
assert_eq!(model.get_row_height(0, 1), Ok(2.0 * DEFAULT_ROW_HEIGHT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_row_evaluates() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.set_user_input(0, 1, 1, "42").unwrap();
|
||||
model.set_user_input(0, 1, 2, "=A1*2").unwrap();
|
||||
|
||||
assert!(model.insert_row(0, 1).is_ok());
|
||||
assert_eq!(model.get_formatted_cell_value(0, 2, 2).unwrap(), "84");
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 1, 2).unwrap(), "84");
|
||||
model.redo().unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 2, 2).unwrap(), "84");
|
||||
|
||||
model.delete_row(0, 1).unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 1, 2).unwrap(), "84");
|
||||
assert_eq!(model.get_cell_content(0, 1, 2).unwrap(), "=A1*2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_column_evaluates() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.set_user_input(0, 1, 1, "42").unwrap();
|
||||
model.set_user_input(0, 10, 1, "=A1*2").unwrap();
|
||||
|
||||
assert!(model.insert_column(0, 1).is_ok());
|
||||
assert_eq!(model.get_formatted_cell_value(0, 10, 2).unwrap(), "84");
|
||||
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 10, 1).unwrap(), "84");
|
||||
model.redo().unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 10, 2).unwrap(), "84");
|
||||
|
||||
model.delete_column(0, 1).unwrap();
|
||||
assert_eq!(model.get_formatted_cell_value(0, 10, 1).unwrap(), "84");
|
||||
assert_eq!(model.get_cell_content(0, 10, 1).unwrap(), "=A1*2");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::UserModel;
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn basic_tests() {
|
||||
|
||||
52
base/src/test/user_model/test_sheets_undo_redo.rs
Normal file
52
base/src/test/user_model/test_sheets_undo_redo.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::UserModel;
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn basic_undo_redo() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
|
||||
model.new_sheet().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 1);
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
{
|
||||
let props = model.get_worksheets_properties();
|
||||
assert_eq!(props.len(), 1);
|
||||
let view = model.get_selected_view();
|
||||
assert_eq!(view.sheet, 0);
|
||||
}
|
||||
|
||||
model.redo().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 1);
|
||||
{
|
||||
let props = model.get_worksheets_properties();
|
||||
assert_eq!(props.len(), 2);
|
||||
let view = model.get_selected_view();
|
||||
|
||||
assert_eq!(view.sheet, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_undo() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
|
||||
model.new_sheet().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 1);
|
||||
model.set_user_input(1, 1, 1, "42").unwrap();
|
||||
model.set_user_input(1, 1, 2, "=A1*2").unwrap();
|
||||
model.delete_sheet(1).unwrap();
|
||||
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 1);
|
||||
model.redo().unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
expressions::types::Area,
|
||||
types::{Alignment, HorizontalAlignment, VerticalAlignment},
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -436,3 +436,47 @@ fn false_removes_value() {
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_clear_formatting() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let range = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
// bold
|
||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
||||
model
|
||||
.update_range_style(&range, "alignment.horizontal", "centerContinuous")
|
||||
.unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(style.font.b);
|
||||
assert_eq!(
|
||||
style.alignment.unwrap().horizontal,
|
||||
HorizontalAlignment::CenterContinuous
|
||||
);
|
||||
|
||||
model.range_clear_all(&range).unwrap();
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
assert_eq!(style.alignment, None);
|
||||
|
||||
model.undo().unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(style.font.b);
|
||||
assert_eq!(
|
||||
style.alignment.unwrap().horizontal,
|
||||
HorizontalAlignment::CenterContinuous
|
||||
);
|
||||
model.redo().unwrap();
|
||||
|
||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
assert_eq!(style.alignment, None);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{test::util::new_empty_model, UserModel};
|
||||
use crate::{UserModel, test::util::new_empty_model};
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut model1 = UserModel::from_model(new_empty_model());
|
||||
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||
model1.set_column_width(0, 3, width).unwrap();
|
||||
model1.set_columns_width(0, 3, 3, width).unwrap();
|
||||
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||
|
||||
let model_bytes = model1.to_bytes();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{test::util::new_empty_model, UserModel};
|
||||
use crate::{UserModel, test::util::new_empty_model};
|
||||
|
||||
#[test]
|
||||
fn simple_undo_redo() {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{LAST_COLUMN, LAST_ROW},
|
||||
test::util::new_empty_model,
|
||||
user_model::SelectedView,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
UserModel,
|
||||
constants::{DEFAULT_ROW_HEIGHT, DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH},
|
||||
test::util::new_empty_model,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{expressions::types::Area, types::Border, BorderArea, UserModel};
|
||||
use crate::{BorderArea, UserModel, expressions::types::Area, types::Border};
|
||||
|
||||
impl UserModel {
|
||||
pub fn _set_cell_border(&mut self, cell: &str, color: &str) {
|
||||
|
||||
@@ -323,6 +323,19 @@ pub struct Style {
|
||||
pub quote_prefix: bool,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
alignment: None,
|
||||
num_fmt: "general".to_string(),
|
||||
fill: Fill::default(),
|
||||
font: Font::default(),
|
||||
border: Border::default(),
|
||||
quote_prefix: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct NumFmt {
|
||||
pub num_fmt_id: i32,
|
||||
|
||||
@@ -50,8 +50,9 @@ impl Units {
|
||||
fn get_units_from_format_string(num_fmt: &str) -> Option<Units> {
|
||||
let mut parser = Parser::new(num_fmt);
|
||||
parser.parse();
|
||||
let parts = parser.parts.first()?;
|
||||
// We only care about the first part (positive number)
|
||||
match &parser.parts[0] {
|
||||
match parts {
|
||||
ParsePart::Number(part) => {
|
||||
if part.percent > 0 {
|
||||
Some(Units::Percentage {
|
||||
|
||||
507
base/src/user_model/border.rs
Normal file
507
base/src/user_model/border.rs
Normal file
@@ -0,0 +1,507 @@
|
||||
use crate::{
|
||||
constants::{LAST_COLUMN, LAST_ROW},
|
||||
expressions::types::Area,
|
||||
};
|
||||
|
||||
use super::{
|
||||
BorderArea, UserModel, border_utils::is_max_border, common::BorderType, history::Diff,
|
||||
};
|
||||
|
||||
impl UserModel {
|
||||
fn update_single_cell_border(
|
||||
&mut self,
|
||||
border_area: &BorderArea,
|
||||
cell: (u32, i32, i32),
|
||||
range: (i32, i32, i32, i32),
|
||||
diff_list: &mut Vec<Diff>,
|
||||
) -> Result<(), String> {
|
||||
let (sheet, row, column) = cell;
|
||||
let (first_row, first_column, last_row, last_column) = range;
|
||||
|
||||
let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
|
||||
let mut new_value = match &old_value {
|
||||
Some(value) => value.clone(),
|
||||
None => Default::default(),
|
||||
};
|
||||
match border_area.r#type {
|
||||
BorderType::All => {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
BorderType::Inner => {
|
||||
if row != first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row != last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
if column != first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column != last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Outer => {
|
||||
if row == first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row == last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
if column == first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column == last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Top => {
|
||||
if row == first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Right => {
|
||||
if column == last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Bottom => {
|
||||
if row == last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Left => {
|
||||
if column == first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::CenterH => {
|
||||
if row != first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row != last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::CenterV => {
|
||||
if column != first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column != last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::None => {
|
||||
new_value.border.top = None;
|
||||
new_value.border.right = None;
|
||||
new_value.border.bottom = None;
|
||||
new_value.border.left = None;
|
||||
}
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &new_value)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
new_value: Box::new(new_value),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_rows_with_border(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
first_row: i32,
|
||||
last_row: i32,
|
||||
border_area: &BorderArea,
|
||||
) -> Result<(), String> {
|
||||
let mut diff_list = Vec::new();
|
||||
for row in first_row..=last_row {
|
||||
let old_value = self.model.get_row_style(sheet, row)?;
|
||||
let mut new_value = match &old_value {
|
||||
Some(value) => value.clone(),
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
match border_area.r#type {
|
||||
BorderType::All => {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
BorderType::Inner => {
|
||||
if row != first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row != last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Outer => {
|
||||
if row == first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row == last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Top => {
|
||||
if row == first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Right => {
|
||||
// noop
|
||||
}
|
||||
BorderType::Bottom => {
|
||||
if row == last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Left => {
|
||||
// noop
|
||||
}
|
||||
BorderType::CenterH => {
|
||||
if row != first_row {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
if row != last_row {
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::CenterV => {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
BorderType::None => {
|
||||
new_value.border.top = None;
|
||||
new_value.border.right = None;
|
||||
new_value.border.bottom = None;
|
||||
new_value.border.left = None;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to go throw each non-empty cell in the row
|
||||
let columns: Vec<i32> = self
|
||||
.model
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.sheet_data
|
||||
.get(&row)
|
||||
.map(|row_data| row_data.keys().copied().collect())
|
||||
.unwrap_or_default();
|
||||
for column in columns {
|
||||
self.update_single_cell_border(
|
||||
border_area,
|
||||
(sheet, row, column),
|
||||
(first_row, 1, last_row, LAST_COLUMN),
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.model.set_row_style(sheet, row, &new_value)?;
|
||||
diff_list.push(Diff::SetRowStyle {
|
||||
sheet,
|
||||
row,
|
||||
old_value: Box::new(old_value),
|
||||
new_value: Box::new(new_value),
|
||||
});
|
||||
}
|
||||
// TODO: We need to check the rows above and below. also any non empty cell in the rows above and below.
|
||||
self.push_diff_list(diff_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_columns_with_border(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
first_column: i32,
|
||||
last_column: i32,
|
||||
border_area: &BorderArea,
|
||||
) -> Result<(), String> {
|
||||
let mut diff_list = Vec::new();
|
||||
// We need all the rows in the column to update the style
|
||||
// NB: This is too much, this is all the rows that have values
|
||||
let data_rows: Vec<i32> = self
|
||||
.model
|
||||
.workbook
|
||||
.worksheet(sheet)?
|
||||
.sheet_data
|
||||
.keys()
|
||||
.copied()
|
||||
.collect();
|
||||
let styled_rows = &self.model.workbook.worksheet(sheet)?.rows.clone();
|
||||
for column in first_column..=last_column {
|
||||
let old_value = self.model.get_column_style(sheet, column)?;
|
||||
let mut new_value = match &old_value {
|
||||
Some(value) => value.clone(),
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
match border_area.r#type {
|
||||
BorderType::All => {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
BorderType::Inner => {
|
||||
if column != first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column != last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Outer => {
|
||||
if column == first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column == last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Top => {
|
||||
// noop
|
||||
}
|
||||
BorderType::Right => {
|
||||
if column == last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::Bottom => {
|
||||
// noop
|
||||
}
|
||||
BorderType::Left => {
|
||||
if column == first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::CenterH => {
|
||||
new_value.border.top = Some(border_area.item.clone());
|
||||
new_value.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
BorderType::CenterV => {
|
||||
if column != first_column {
|
||||
new_value.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
if column != last_column {
|
||||
new_value.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
}
|
||||
BorderType::None => {
|
||||
new_value.border.top = None;
|
||||
new_value.border.right = None;
|
||||
new_value.border.bottom = None;
|
||||
new_value.border.left = None;
|
||||
}
|
||||
}
|
||||
// We need to go through each non empty cell in the column
|
||||
for &row in &data_rows {
|
||||
if let Some(data_row) = self.model.workbook.worksheet(sheet)?.sheet_data.get(&row) {
|
||||
if data_row.get(&column).is_some() {
|
||||
self.update_single_cell_border(
|
||||
border_area,
|
||||
(sheet, row, column),
|
||||
(1, first_column, LAST_ROW, last_column),
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We also need to overwrite those that have a row style
|
||||
for row_s in styled_rows.iter() {
|
||||
let row = row_s.r;
|
||||
self.update_single_cell_border(
|
||||
border_area,
|
||||
(sheet, row, column),
|
||||
(1, first_column, LAST_ROW, last_column),
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.model.set_column_style(sheet, column, &new_value)?;
|
||||
diff_list.push(Diff::SetColumnStyle {
|
||||
sheet,
|
||||
column,
|
||||
old_value: Box::new(old_value),
|
||||
new_value: Box::new(new_value),
|
||||
});
|
||||
}
|
||||
// We need to check the borders of the column to the left and the column to the right
|
||||
// We also need to check every non-empty cell in the columns to the left and right
|
||||
self.push_diff_list(diff_list);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the border in an area of cells.
|
||||
/// When setting the border we need to check if the adjacent cells have a "heavier" border
|
||||
/// If that is the case we need to change it
|
||||
pub fn set_area_with_border(
|
||||
&mut self,
|
||||
range: &Area,
|
||||
border_area: &BorderArea,
|
||||
) -> Result<(), String> {
|
||||
let sheet = range.sheet;
|
||||
let first_row = range.row;
|
||||
let first_column = range.column;
|
||||
let last_row = first_row + range.height - 1;
|
||||
let last_column = first_column + range.width - 1;
|
||||
if first_row == 1 && last_row == LAST_ROW {
|
||||
// full columns
|
||||
self.set_columns_with_border(sheet, first_column, last_column, border_area)?;
|
||||
return Ok(());
|
||||
}
|
||||
if first_column == 1 && last_column == LAST_COLUMN {
|
||||
// full rows
|
||||
self.set_rows_with_border(sheet, first_row, last_row, border_area)?;
|
||||
return Ok(());
|
||||
}
|
||||
let mut diff_list = Vec::new();
|
||||
for row in first_row..=last_row {
|
||||
for column in first_column..=last_column {
|
||||
self.update_single_cell_border(
|
||||
border_area,
|
||||
(sheet, row, column),
|
||||
(first_row, first_column, last_row, last_column),
|
||||
&mut diff_list,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// bottom of the cells above the first
|
||||
if first_row > 1
|
||||
&& [
|
||||
BorderType::All,
|
||||
BorderType::None,
|
||||
BorderType::Outer,
|
||||
BorderType::Top,
|
||||
]
|
||||
.contains(&border_area.r#type)
|
||||
{
|
||||
let row = first_row - 1;
|
||||
for column in first_column..=last_column {
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
if is_max_border(Some(&border_area.item), old_value.border.bottom.as_ref()) {
|
||||
let mut style = old_value.clone();
|
||||
if border_area.r#type == BorderType::None {
|
||||
style.border.bottom = None;
|
||||
} else {
|
||||
style.border.bottom = Some(border_area.item.clone());
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cells to the right
|
||||
if last_column < LAST_COLUMN
|
||||
&& [
|
||||
BorderType::All,
|
||||
BorderType::None,
|
||||
BorderType::Outer,
|
||||
BorderType::Right,
|
||||
]
|
||||
.contains(&border_area.r#type)
|
||||
{
|
||||
let column = last_column + 1;
|
||||
for row in first_row..=last_row {
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
// If the border in the adjacent cell is "heavier" we change it
|
||||
if is_max_border(Some(&border_area.item), old_value.border.left.as_ref()) {
|
||||
let mut style = old_value.clone();
|
||||
if border_area.r#type == BorderType::None {
|
||||
style.border.left = None;
|
||||
} else {
|
||||
style.border.left = Some(border_area.item.clone());
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cells bellow
|
||||
if last_row < LAST_ROW
|
||||
&& [
|
||||
BorderType::All,
|
||||
BorderType::None,
|
||||
BorderType::Outer,
|
||||
BorderType::Bottom,
|
||||
]
|
||||
.contains(&border_area.r#type)
|
||||
{
|
||||
let row = last_row + 1;
|
||||
for column in first_column..=last_column {
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
if is_max_border(Some(&border_area.item), old_value.border.top.as_ref()) {
|
||||
let mut style = old_value.clone();
|
||||
if border_area.r#type == BorderType::None {
|
||||
style.border.top = None;
|
||||
} else {
|
||||
style.border.top = Some(border_area.item.clone());
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cells to the left
|
||||
if first_column > 1
|
||||
&& [
|
||||
BorderType::All,
|
||||
BorderType::None,
|
||||
BorderType::Outer,
|
||||
BorderType::Left,
|
||||
]
|
||||
.contains(&border_area.r#type)
|
||||
{
|
||||
let column = first_column - 1;
|
||||
for row in first_row..=last_row {
|
||||
let old_value = self.model.get_style_for_cell(sheet, row, column)?;
|
||||
if is_max_border(Some(&border_area.item), old_value.border.right.as_ref()) {
|
||||
let mut style = old_value.clone();
|
||||
if border_area.r#type == BorderType::None {
|
||||
style.border.right = None;
|
||||
} else {
|
||||
style.border.right = Some(border_area.item.clone());
|
||||
}
|
||||
self.model.set_cell_style(sheet, row, column, &style)?;
|
||||
diff_list.push(Diff::SetCellStyle {
|
||||
sheet,
|
||||
row,
|
||||
column,
|
||||
old_value: Box::new(Some(old_value)),
|
||||
new_value: Box::new(style),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.push_diff_list(diff_list);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use bitcode::{Decode, Encode};
|
||||
|
||||
use crate::types::{Cell, Col, Row, SheetState, Style};
|
||||
use crate::types::{Cell, Col, Row, SheetState, Style, Worksheet};
|
||||
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub(crate) struct RowData {
|
||||
@@ -39,11 +39,17 @@ pub(crate) enum Diff {
|
||||
old_value: Box<Option<Cell>>,
|
||||
old_style: Box<Style>,
|
||||
},
|
||||
CellClearFormatting {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
old_style: Box<Option<Style>>,
|
||||
},
|
||||
SetCellStyle {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
old_value: Box<Style>,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
// Column and Row diffs
|
||||
@@ -59,6 +65,28 @@ pub(crate) enum Diff {
|
||||
new_value: f64,
|
||||
old_value: f64,
|
||||
},
|
||||
SetColumnStyle {
|
||||
sheet: u32,
|
||||
column: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
SetRowStyle {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
new_value: Box<Style>,
|
||||
},
|
||||
DeleteColumnStyle {
|
||||
sheet: u32,
|
||||
column: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
},
|
||||
DeleteRowStyle {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
old_value: Box<Option<Style>>,
|
||||
},
|
||||
InsertRow {
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
@@ -77,6 +105,10 @@ pub(crate) enum Diff {
|
||||
column: i32,
|
||||
old_data: Box<ColumnData>,
|
||||
},
|
||||
DeleteSheet {
|
||||
sheet: u32,
|
||||
old_data: Box<Worksheet>,
|
||||
},
|
||||
SetFrozenRowsCount {
|
||||
sheet: u32,
|
||||
new_value: i32,
|
||||
@@ -87,9 +119,6 @@ pub(crate) enum Diff {
|
||||
new_value: i32,
|
||||
old_value: i32,
|
||||
},
|
||||
DeleteSheet {
|
||||
sheet: u32,
|
||||
},
|
||||
NewSheet {
|
||||
index: u32,
|
||||
name: String,
|
||||
@@ -168,11 +197,6 @@ impl History {
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.redo_stack = vec![];
|
||||
self.undo_stack = vec![];
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod border;
|
||||
mod border_utils;
|
||||
mod common;
|
||||
mod history;
|
||||
|
||||
@@ -156,7 +156,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::language::get_language;
|
||||
use crate::locale::{get_locale, Locale};
|
||||
use crate::locale::{Locale, get_locale};
|
||||
|
||||
fn get_test_locale() -> &'static Locale {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Workbook {
|
||||
}
|
||||
|
||||
/// Returns the a list of defined names in the workbook with their scope
|
||||
pub(crate) fn get_defined_names_with_scope(&self) -> Vec<(String, Option<u32>, String)> {
|
||||
pub fn get_defined_names_with_scope(&self) -> Vec<(String, Option<u32>, String)> {
|
||||
let sheet_id_index: Vec<u32> = self.worksheets.iter().map(|s| s.sheet_id).collect();
|
||||
|
||||
let defined_names = self
|
||||
|
||||
@@ -108,37 +108,120 @@ impl Worksheet {
|
||||
self.cols = vec![Col {
|
||||
min: 1,
|
||||
max: constants::LAST_COLUMN,
|
||||
width: constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR,
|
||||
custom_width: true,
|
||||
width: constants::DEFAULT_COLUMN_WIDTH,
|
||||
custom_width: false,
|
||||
style: Some(style_index),
|
||||
}];
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
|
||||
let width = constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR;
|
||||
let width = self
|
||||
.get_column_width(column)
|
||||
.unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
|
||||
self.set_column_width_and_style(column, width, Some(style_index))
|
||||
}
|
||||
|
||||
pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
|
||||
// FIXME: This is a HACK
|
||||
let custom_format = style_index != 0;
|
||||
for r in self.rows.iter_mut() {
|
||||
if r.r == row {
|
||||
r.s = style_index;
|
||||
r.custom_format = true;
|
||||
r.custom_format = custom_format;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.rows.push(Row {
|
||||
height: constants::DEFAULT_ROW_HEIGHT / constants::ROW_HEIGHT_FACTOR,
|
||||
r: row,
|
||||
custom_format: true,
|
||||
custom_height: true,
|
||||
custom_format,
|
||||
custom_height: false,
|
||||
s: style_index,
|
||||
hidden: false,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_row_style(&mut self, row: i32) -> Result<(), String> {
|
||||
let mut index = None;
|
||||
for (i, r) in self.rows.iter().enumerate() {
|
||||
if r.r == row {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(i) = index {
|
||||
if let Some(r) = self.rows.get_mut(i) {
|
||||
r.s = 0;
|
||||
r.custom_format = false;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_column_style(&mut self, column: i32) -> Result<(), String> {
|
||||
if !is_valid_column_number(column) {
|
||||
return Err(format!("Column number '{column}' is not valid."));
|
||||
}
|
||||
let cols = &mut self.cols;
|
||||
|
||||
let mut index = 0;
|
||||
let mut split = false;
|
||||
for c in cols.iter_mut() {
|
||||
let min = c.min;
|
||||
let max = c.max;
|
||||
if min <= column && column <= max {
|
||||
//
|
||||
split = true;
|
||||
break;
|
||||
}
|
||||
if column < min {
|
||||
// We passed, there is nothing to delete
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if split {
|
||||
let min = cols[index].min;
|
||||
let max = cols[index].max;
|
||||
let custom_width = cols[index].custom_width;
|
||||
let width = cols[index].width;
|
||||
let pre = Col {
|
||||
min,
|
||||
max: column - 1,
|
||||
width,
|
||||
custom_width,
|
||||
style: cols[index].style,
|
||||
};
|
||||
let col = Col {
|
||||
min: column,
|
||||
max: column,
|
||||
width,
|
||||
custom_width,
|
||||
style: None,
|
||||
};
|
||||
let post = Col {
|
||||
min: column + 1,
|
||||
max,
|
||||
width,
|
||||
custom_width,
|
||||
style: cols[index].style,
|
||||
};
|
||||
cols.remove(index);
|
||||
if column != max {
|
||||
cols.insert(index, post);
|
||||
}
|
||||
if custom_width {
|
||||
cols.insert(index, col);
|
||||
}
|
||||
if column != min {
|
||||
cols.insert(index, pre);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_cell_style(
|
||||
&mut self,
|
||||
row: i32,
|
||||
@@ -285,11 +368,12 @@ impl Worksheet {
|
||||
|
||||
/// Changes the width of a column.
|
||||
/// * If the column does not a have a width we simply add it
|
||||
/// * If it has, it might be part of a range and we ned to split the range.
|
||||
/// * If it has, it might be part of a range and we need to split the range.
|
||||
///
|
||||
/// Fails if column index is outside allowed range or width is negative.
|
||||
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
|
||||
self.set_column_width_and_style(column, width, None)
|
||||
let style = self.get_column_style(column)?;
|
||||
self.set_column_width_and_style(column, width, style)
|
||||
}
|
||||
|
||||
pub(crate) fn set_column_width_and_style(
|
||||
@@ -309,7 +393,7 @@ impl Worksheet {
|
||||
min: column,
|
||||
max: column,
|
||||
width: width / constants::COLUMN_WIDTH_FACTOR,
|
||||
custom_width: true,
|
||||
custom_width: width != constants::DEFAULT_COLUMN_WIDTH,
|
||||
style,
|
||||
};
|
||||
let mut index = 0;
|
||||
@@ -319,7 +403,9 @@ impl Worksheet {
|
||||
let max = c.max;
|
||||
if min <= column && column <= max {
|
||||
if min == column && max == column {
|
||||
c.style = style;
|
||||
c.width = width / constants::COLUMN_WIDTH_FACTOR;
|
||||
c.custom_width = width != constants::DEFAULT_COLUMN_WIDTH;
|
||||
return Ok(());
|
||||
}
|
||||
split = true;
|
||||
@@ -383,6 +469,23 @@ impl Worksheet {
|
||||
Ok(constants::DEFAULT_COLUMN_WIDTH)
|
||||
}
|
||||
|
||||
/// Returns the column style index if present
|
||||
pub fn get_column_style(&self, column: i32) -> Result<Option<i32>, String> {
|
||||
if !is_valid_column_number(column) {
|
||||
return Err(format!("Column number '{column}' is not valid."));
|
||||
}
|
||||
|
||||
let cols = &self.cols;
|
||||
for col in cols {
|
||||
let min = col.min;
|
||||
let max = col.max;
|
||||
if column >= min && column <= max {
|
||||
return Ok(col.style);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Returns non empty cells in a column
|
||||
pub fn column_cell_references(&self, column: i32) -> Result<Vec<CellReferenceIndex>, String> {
|
||||
let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "ironcalc_nodejs"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import test from 'ava'
|
||||
|
||||
import { Model } from '../index.js';
|
||||
import { UserModel, Model } from '../index.js';
|
||||
|
||||
test('sum from native', (t) => {
|
||||
const model = new Model("Workbook1", "en", "UTC");
|
||||
test('User Model smoke test', (t) => {
|
||||
const model = new UserModel("Workbook1", "en", "UTC");
|
||||
|
||||
model.setUserInput(0, 1, 1, "=1+1");
|
||||
t.is(model.getFormattedCellValue(0, 1, 1), '2');
|
||||
});
|
||||
|
||||
|
||||
test('Raw API smoke test', (t) => {
|
||||
const model = new Model("Workbook1", "en", "UTC");
|
||||
|
||||
model.setUserInput(0, 1, 1, "=1+1");
|
||||
model.evaluate();
|
||||
t.is(model.getFormattedCellValue(0, 1, 1), '2');
|
||||
});
|
||||
|
||||
|
||||
BIN
bindings/nodejs/example.xlsx
Normal file
BIN
bindings/nodejs/example.xlsx
Normal file
Binary file not shown.
40
bindings/nodejs/index.d.ts
vendored
40
bindings/nodejs/index.d.ts
vendored
@@ -5,7 +5,45 @@
|
||||
|
||||
export declare class Model {
|
||||
constructor(name: string, locale: string, timezone: string)
|
||||
static fromBytes(bytes: Uint8Array): Model
|
||||
static fromXlsx(filePath: string, locale: string, tz: string): Model
|
||||
static fromIcalc(fileName: string): Model
|
||||
saveToXlsx(file: string): void
|
||||
saveToIcalc(file: string): void
|
||||
evaluate(): void
|
||||
setUserInput(sheet: number, row: number, column: number, value: string): void
|
||||
clearCellContents(sheet: number, row: number, column: number): void
|
||||
getCellContent(sheet: number, row: number, column: number): string
|
||||
getCellType(sheet: number, row: number, column: number): number
|
||||
getFormattedCellValue(sheet: number, row: number, column: number): string
|
||||
setCellStyle(sheet: number, row: number, column: number, style: unknown): void
|
||||
getCellStyle(sheet: number, row: number, column: number): unknown
|
||||
insertRows(sheet: number, row: number, rowCount: number): void
|
||||
insertColumns(sheet: number, column: number, columnCount: number): void
|
||||
deleteRows(sheet: number, row: number, rowCount: number): void
|
||||
deleteColumns(sheet: number, column: number, columnCount: number): void
|
||||
getColumnWidth(sheet: number, column: number): number
|
||||
getRowHeight(sheet: number, row: number): number
|
||||
setColumnWidth(sheet: number, column: number, width: number): void
|
||||
setRowHeight(sheet: number, row: number, height: number): void
|
||||
getFrozenColumnsCount(sheet: number): number
|
||||
getFrozenRowsCount(sheet: number): number
|
||||
setFrozenColumnsCount(sheet: number, columnCount: number): void
|
||||
setFrozenRowsCount(sheet: number, rowCount: number): void
|
||||
getWorksheetsProperties(): unknown
|
||||
setSheetColor(sheet: number, color: string): void
|
||||
addSheet(sheetName: string): void
|
||||
newSheet(): void
|
||||
deleteSheet(sheet: number): void
|
||||
renameSheet(sheet: number, newName: string): void
|
||||
getDefinedNameList(): unknown
|
||||
newDefinedName(name: string, scope: number | undefined | null, formula: string): void
|
||||
updateDefinedName(name: string, scope: number | undefined | null, newName: string, newScope: number | undefined | null, newFormula: string): void
|
||||
deleteDefinedName(name: string, scope?: number | undefined | null): void
|
||||
testPanic(): void
|
||||
}
|
||||
export declare class UserModel {
|
||||
constructor(name: string, locale: string, timezone: string)
|
||||
static fromBytes(bytes: Uint8Array): UserModel
|
||||
canUndo(): boolean
|
||||
canRedo(): boolean
|
||||
pauseEvaluation(): void
|
||||
|
||||
@@ -310,6 +310,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { Model } = nativeBinding
|
||||
const { Model, UserModel } = nativeBinding
|
||||
|
||||
module.exports.Model = Model
|
||||
module.exports.UserModel = UserModel
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
import { Model } from './index.js'
|
||||
|
||||
const model = new Model("Workbook1", "en", "UTC");
|
||||
import { UserModel, Model } from './index.js'
|
||||
|
||||
model.setUserInput(0, 1, 1, "=1+1");
|
||||
let t = model.getFormattedCellValue(0, 1, 1);
|
||||
|
||||
console.log('From native', t);
|
||||
function testUserModel() {
|
||||
const model = new UserModel("Workbook1", "en", "UTC");
|
||||
|
||||
let t2 = model.getCellStyle(0, 1, 1);
|
||||
console.log('From native', t2);
|
||||
model.setUserInput(0, 1, 1, "=1+1");
|
||||
let t = model.getFormattedCellValue(0, 1, 1);
|
||||
|
||||
console.log('From native', t);
|
||||
|
||||
let t2 = model.getCellStyle(0, 1, 1);
|
||||
console.log('From native', t2);
|
||||
}
|
||||
|
||||
function testModel() {
|
||||
const model = Model.fromXlsx("example.xlsx", "en", "UTC");
|
||||
const style = model.getCellStyle(0, 1, 6);
|
||||
console.log(style);
|
||||
|
||||
const quantum = model.getFormattedCellValue(0, 14, 4);
|
||||
console.log(quantum);
|
||||
|
||||
}
|
||||
|
||||
|
||||
testUserModel();
|
||||
testModel();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ironcalc/nodejs",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
tab_spaces = 2
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
@@ -1,623 +1,8 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
use napi::{self, bindgen_prelude::*, JsUnknown, Result};
|
||||
mod model;
|
||||
mod user_model;
|
||||
|
||||
use ironcalc::base::{
|
||||
expressions::types::Area,
|
||||
types::{CellType, Style},
|
||||
BorderArea, ClipboardData, UserModel as BaseModel,
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
}
|
||||
|
||||
fn to_js_error(error: String) -> Error {
|
||||
Error::new(Status::Unknown, error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct Model {
|
||||
model: BaseModel,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl Model {
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String, locale: String, timezone: String) -> Result<Self> {
|
||||
let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?;
|
||||
Ok(Self { model })
|
||||
}
|
||||
|
||||
#[napi(factory)]
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Model> {
|
||||
let model = BaseModel::from_bytes(bytes).map_err(to_js_error)?;
|
||||
Ok(Model { model })
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Result<()> {
|
||||
self.model.undo().map_err(to_js_error)
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Result<()> {
|
||||
self.model.redo().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "canUndo")]
|
||||
pub fn can_undo(&self) -> bool {
|
||||
self.model.can_undo()
|
||||
}
|
||||
|
||||
#[napi(js_name = "canRedo")]
|
||||
pub fn can_redo(&self) -> bool {
|
||||
self.model.can_redo()
|
||||
}
|
||||
|
||||
#[napi(js_name = "pauseEvaluation")]
|
||||
pub fn pause_evaluation(&mut self) {
|
||||
self.model.pause_evaluation()
|
||||
}
|
||||
|
||||
#[napi(js_name = "resumeEvaluation")]
|
||||
pub fn resume_evaluation(&mut self) {
|
||||
self.model.resume_evaluation()
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self) {
|
||||
self.model.evaluate();
|
||||
}
|
||||
|
||||
#[napi(js_name = "flushSendQueue")]
|
||||
pub fn flush_send_queue(&mut self) -> Vec<u8> {
|
||||
self.model.flush_send_queue()
|
||||
}
|
||||
|
||||
#[napi(js_name = "applyExternalDiffs")]
|
||||
pub fn apply_external_diffs(&mut self, diffs: &[u8]) -> Result<()> {
|
||||
self.model.apply_external_diffs(diffs).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellContent")]
|
||||
pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_cell_content(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "newSheet")]
|
||||
pub fn new_sheet(&mut self) -> Result<()> {
|
||||
self.model.new_sheet().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteSheet")]
|
||||
pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.delete_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "hideSheet")]
|
||||
pub fn hide_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.hide_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "unhideSheet")]
|
||||
pub fn unhide_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.unhide_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "renameSheet")]
|
||||
pub fn rename_sheet(&mut self, sheet: u32, name: String) -> Result<()> {
|
||||
self.model.rename_sheet(sheet, &name).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSheetColor")]
|
||||
pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_sheet_color(sheet, &color)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "rangeClearAll")]
|
||||
pub fn range_clear_all(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self.model.range_clear_all(&range).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "rangeClearContents")]
|
||||
pub fn range_clear_contents(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self.model.range_clear_contents(&range).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "insertRow")]
|
||||
pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<()> {
|
||||
self.model.insert_row(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "insertColumn")]
|
||||
pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<()> {
|
||||
self.model.insert_column(sheet, column).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteRow")]
|
||||
pub fn delete_row(&mut self, sheet: u32, row: i32) -> Result<()> {
|
||||
self.model.delete_row(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteColumn")]
|
||||
pub fn delete_column(&mut self, sheet: u32, column: i32) -> Result<()> {
|
||||
self.model.delete_column(sheet, column).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setRowHeight")]
|
||||
pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_row_height(sheet, row, height)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setColumnWidth")]
|
||||
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_column_width(sheet, column, width)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getRowHeight")]
|
||||
pub fn get_row_height(&mut self, sheet: u32, row: i32) -> Result<f64> {
|
||||
self.model.get_row_height(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getColumnWidth")]
|
||||
pub fn get_column_width(&mut self, sheet: u32, column: i32) -> Result<f64> {
|
||||
self
|
||||
.model
|
||||
.get_column_width(sheet, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setUserInput")]
|
||||
pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, input: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_user_input(sheet, row, column, &input)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFormattedCellValue")]
|
||||
pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_formatted_cell_value(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFrozenRowsCount")]
|
||||
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32> {
|
||||
self.model.get_frozen_rows_count(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFrozenColumnsCount")]
|
||||
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32> {
|
||||
self
|
||||
.model
|
||||
.get_frozen_columns_count(sheet)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setFrozenRowsCount")]
|
||||
pub fn set_frozen_rows_count(&mut self, sheet: u32, count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_rows_count(sheet, count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setFrozenColumnsCount")]
|
||||
pub fn set_frozen_columns_count(&mut self, sheet: u32, count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_columns_count(sheet, count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "updateRangeStyle")]
|
||||
pub fn update_range_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
range: JsUnknown,
|
||||
style_path: String,
|
||||
value: String,
|
||||
) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(range)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.update_range_style(&range, &style_path, &value)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellStyle")]
|
||||
pub fn get_cell_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<JsUnknown> {
|
||||
let style = self
|
||||
.model
|
||||
.get_cell_style(sheet, row, column)
|
||||
.map_err(to_js_error)?;
|
||||
|
||||
env
|
||||
.to_js_value(&style)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPasteStyles")]
|
||||
pub fn on_paste_styles(&mut self, env: Env, styles: JsUnknown) -> Result<()> {
|
||||
let styles: &Vec<Vec<Style>> = &env
|
||||
.from_js_value(styles)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self.model.on_paste_styles(styles).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellType")]
|
||||
pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<i32> {
|
||||
Ok(
|
||||
match self
|
||||
.model
|
||||
.get_cell_type(sheet, row, column)
|
||||
.map_err(to_js_error)?
|
||||
{
|
||||
CellType::Number => 1,
|
||||
CellType::Text => 2,
|
||||
CellType::LogicalValue => 4,
|
||||
CellType::ErrorValue => 16,
|
||||
CellType::Array => 64,
|
||||
CellType::CompoundData => 128,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// I don't _think_ serializing to JsUnknown can't fail
|
||||
// FIXME: Remove this clippy directive
|
||||
#[napi(js_name = "getWorksheetsProperties")]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown {
|
||||
env
|
||||
.to_js_value(&self.model.get_worksheets_properties())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getSelectedSheet")]
|
||||
pub fn get_selected_sheet(&self) -> u32 {
|
||||
self.model.get_selected_sheet()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getSelectedCell")]
|
||||
pub fn get_selected_cell(&self) -> Vec<i32> {
|
||||
let (sheet, row, column) = self.model.get_selected_cell();
|
||||
vec![sheet as i32, row, column]
|
||||
}
|
||||
|
||||
// I don't _think_ serializing to JsUnknown can't fail
|
||||
// FIXME: Remove this clippy directive
|
||||
#[napi(js_name = "getSelectedView")]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn get_selected_view(&self, env: Env) -> JsUnknown {
|
||||
env.to_js_value(&self.model.get_selected_view()).unwrap()
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedSheet")]
|
||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.set_selected_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedCell")]
|
||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_selected_cell(row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedRange")]
|
||||
pub fn set_selected_range(
|
||||
&mut self,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setTopLeftVisibleCell")]
|
||||
pub fn set_top_left_visible_cell(&mut self, top_row: i32, top_column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_top_left_visible_cell(top_row, top_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setShowGridLines")]
|
||||
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_show_grid_lines(sheet, show_grid_lines)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getShowGridLines")]
|
||||
pub fn get_show_grid_lines(&mut self, sheet: u32) -> Result<bool> {
|
||||
self.model.get_show_grid_lines(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "autoFillRows")]
|
||||
pub fn auto_fill_rows(&mut self, env: Env, source_area: JsUnknown, to_row: i32) -> Result<()> {
|
||||
let area: Area = env
|
||||
.from_js_value(source_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.auto_fill_rows(&area, to_row)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "autoFillColumns")]
|
||||
pub fn auto_fill_columns(
|
||||
&mut self,
|
||||
env: Env,
|
||||
source_area: JsUnknown,
|
||||
to_column: i32,
|
||||
) -> Result<()> {
|
||||
let area: Area = env
|
||||
.from_js_value(source_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.auto_fill_columns(&area, to_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowRight")]
|
||||
pub fn on_arrow_right(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_right().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowLeft")]
|
||||
pub fn on_arrow_left(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_left().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowUp")]
|
||||
pub fn on_arrow_up(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_up().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowDown")]
|
||||
pub fn on_arrow_down(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_down().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPageDown")]
|
||||
pub fn on_page_down(&mut self) -> Result<()> {
|
||||
self.model.on_page_down().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPageUp")]
|
||||
pub fn on_page_up(&mut self) -> Result<()> {
|
||||
self.model.on_page_up().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setWindowWidth")]
|
||||
pub fn set_window_width(&mut self, window_width: f64) {
|
||||
self.model.set_window_width(window_width);
|
||||
}
|
||||
|
||||
#[napi(js_name = "setWindowHeight")]
|
||||
pub fn set_window_height(&mut self, window_height: f64) {
|
||||
self.model.set_window_height(window_height);
|
||||
}
|
||||
|
||||
#[napi(js_name = "getScrollX")]
|
||||
pub fn get_scroll_x(&self) -> Result<f64> {
|
||||
self.model.get_scroll_x().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getScrollY")]
|
||||
pub fn get_scroll_y(&self) -> Result<f64> {
|
||||
self.model.get_scroll_y().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onExpandSelectedRange")]
|
||||
pub fn on_expand_selected_range(&mut self, key: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.on_expand_selected_range(&key)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onAreaSelecting")]
|
||||
pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.on_area_selecting(target_row, target_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setAreaWithBorder")]
|
||||
pub fn set_area_with_border(
|
||||
&mut self,
|
||||
env: Env,
|
||||
area: JsUnknown,
|
||||
border_area: JsUnknown,
|
||||
) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
let border: BorderArea = env
|
||||
.from_js_value(border_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.set_area_with_border(&range, &border)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi(js_name = "toBytes")]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.model.to_bytes()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getName")]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.model.get_name()
|
||||
}
|
||||
|
||||
#[napi(js_name = "setName")]
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.model.set_name(&name);
|
||||
}
|
||||
|
||||
#[napi(js_name = "copyToClipboard")]
|
||||
pub fn copy_to_clipboard(&self, env: Env) -> Result<JsUnknown> {
|
||||
let data = self
|
||||
.model
|
||||
.copy_to_clipboard()
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
|
||||
env
|
||||
.to_js_value(&data)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "pasteFromClipboard")]
|
||||
pub fn paste_from_clipboard(
|
||||
&mut self,
|
||||
env: Env,
|
||||
source_sheet: u32,
|
||||
source_range: JsUnknown,
|
||||
clipboard: JsUnknown,
|
||||
is_cut: bool,
|
||||
) -> Result<()> {
|
||||
let source_range: (i32, i32, i32, i32) = env
|
||||
.from_js_value(source_range)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
let clipboard: ClipboardData = env
|
||||
.from_js_value(clipboard)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.paste_from_clipboard(source_sheet, source_range, &clipboard, is_cut)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "pasteCsvText")]
|
||||
pub fn paste_csv_string(&mut self, env: Env, area: JsUnknown, csv: String) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.paste_csv_string(&range, &csv)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "getDefinedNameList")]
|
||||
pub fn get_defined_name_list(&self, env: Env) -> Result<JsUnknown> {
|
||||
let data: Vec<DefinedName> = self
|
||||
.model
|
||||
.get_defined_name_list()
|
||||
.iter()
|
||||
.map(|s| DefinedName {
|
||||
name: s.0.to_owned(),
|
||||
scope: s.1,
|
||||
formula: s.2.to_owned(),
|
||||
})
|
||||
.collect();
|
||||
env
|
||||
.to_js_value(&data)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "newDefinedName")]
|
||||
pub fn new_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.new_defined_name(&name, scope, &formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "updateDefinedName")]
|
||||
pub fn update_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
new_name: String,
|
||||
new_scope: Option<u32>,
|
||||
new_formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.update_defined_name(&name, scope, &new_name, new_scope, &new_formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteDefinedName")]
|
||||
pub fn delete_definedname(&mut self, name: String, scope: Option<u32>) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.delete_defined_name(&name, scope)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
}
|
||||
pub use model::Model;
|
||||
pub use user_model::UserModel;
|
||||
|
||||
343
bindings/nodejs/src/model.rs
Normal file
343
bindings/nodejs/src/model.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use napi::{self, JsUnknown, Result, bindgen_prelude::*};
|
||||
use serde::Serialize;
|
||||
|
||||
use ironcalc::{
|
||||
base::{
|
||||
Model as BaseModel,
|
||||
types::{CellType, Style},
|
||||
},
|
||||
error::XlsxError,
|
||||
export::{save_to_icalc, save_to_xlsx},
|
||||
import::{load_from_icalc, load_from_xlsx},
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
}
|
||||
|
||||
fn to_js_error(error: String) -> Error {
|
||||
Error::new(Status::Unknown, error)
|
||||
}
|
||||
|
||||
fn to_node_error(error: XlsxError) -> Error {
|
||||
Error::new(Status::Unknown, error.to_string())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct Model {
|
||||
model: BaseModel,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl Model {
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String, locale: String, timezone: String) -> Result<Self> {
|
||||
let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?;
|
||||
Ok(Self { model })
|
||||
}
|
||||
|
||||
#[napi(factory)]
|
||||
pub fn from_xlsx(file_path: String, locale: String, tz: String) -> Result<Model> {
|
||||
let model = load_from_xlsx(&file_path, &locale, &tz)
|
||||
.map_err(|error| Error::new(Status::Unknown, error.to_string()))?;
|
||||
Ok(Self { model })
|
||||
}
|
||||
|
||||
#[napi(factory)]
|
||||
pub fn from_icalc(file_name: String) -> Result<Model> {
|
||||
let model = load_from_icalc(&file_name)
|
||||
.map_err(|error| Error::new(Status::Unknown, error.to_string()))?;
|
||||
Ok(Self { model })
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn save_to_xlsx(&self, file: String) -> Result<()> {
|
||||
save_to_xlsx(&self.model, &file).map_err(to_node_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn save_to_icalc(&self, file: String) -> Result<()> {
|
||||
save_to_icalc(&self.model, &file).map_err(to_node_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn evaluate(&mut self) {
|
||||
self.model.evaluate();
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, value: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_user_input(sheet, row, column, value)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn clear_cell_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.cell_clear_contents(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_cell_content(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellType")]
|
||||
pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<i32> {
|
||||
Ok(
|
||||
match self
|
||||
.model
|
||||
.get_cell_type(sheet, row, column)
|
||||
.map_err(to_js_error)?
|
||||
{
|
||||
CellType::Number => 1,
|
||||
CellType::Text => 2,
|
||||
CellType::LogicalValue => 4,
|
||||
CellType::ErrorValue => 16,
|
||||
CellType::Array => 64,
|
||||
CellType::CompoundData => 128,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_formatted_cell_value(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_cell_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
style: JsUnknown,
|
||||
) -> Result<()> {
|
||||
let style: Style = env
|
||||
.from_js_value(style)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.set_cell_style(sheet, row, column, &style)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellStyle")]
|
||||
pub fn get_cell_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<JsUnknown> {
|
||||
let style = self
|
||||
.model
|
||||
.get_style_for_cell(sheet, row, column)
|
||||
.map_err(to_js_error)?;
|
||||
|
||||
env
|
||||
.to_js_value(&style)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.insert_rows(sheet, row, row_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn insert_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.insert_columns(sheet, column, column_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.delete_rows(sheet, row, row_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn delete_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.delete_columns(sheet, column, column_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64> {
|
||||
self
|
||||
.model
|
||||
.get_column_width(sheet, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64> {
|
||||
self.model.get_row_height(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_column_width(sheet, column, width)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_row_height(sheet, row, height)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32> {
|
||||
self
|
||||
.model
|
||||
.get_frozen_columns_count(sheet)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32> {
|
||||
self.model.get_frozen_rows_count(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_frozen_columns_count(&mut self, sheet: u32, column_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_columns(sheet, column_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_frozen_rows_count(&mut self, sheet: u32, row_count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_rows(sheet, row_count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
// I don't _think_ serializing to JsUnknown can't fail
|
||||
// FIXME: Remove this clippy directive
|
||||
#[napi(js_name = "getWorksheetsProperties")]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown {
|
||||
env
|
||||
.to_js_value(&self.model.get_worksheets_properties())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_sheet_color(sheet, &color)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn add_sheet(&mut self, sheet_name: String) -> Result<()> {
|
||||
self.model.add_sheet(&sheet_name).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn new_sheet(&mut self) {
|
||||
self.model.new_sheet();
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.delete_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn rename_sheet(&mut self, sheet: u32, new_name: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.rename_sheet_by_index(sheet, &new_name)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getDefinedNameList")]
|
||||
pub fn get_defined_name_list(&self, env: Env) -> Result<JsUnknown> {
|
||||
let data: Vec<DefinedName> = self
|
||||
.model
|
||||
.workbook
|
||||
.get_defined_names_with_scope()
|
||||
.iter()
|
||||
.map(|s| DefinedName {
|
||||
name: s.0.to_owned(),
|
||||
scope: s.1,
|
||||
formula: s.2.to_owned(),
|
||||
})
|
||||
.collect();
|
||||
env
|
||||
.to_js_value(&data)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "newDefinedName")]
|
||||
pub fn new_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.new_defined_name(&name, scope, &formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "updateDefinedName")]
|
||||
pub fn update_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
new_name: String,
|
||||
new_scope: Option<u32>,
|
||||
new_formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.update_defined_name(&name, scope, &new_name, new_scope, &new_formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteDefinedName")]
|
||||
pub fn delete_definedname(&mut self, name: String, scope: Option<u32>) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.delete_defined_name(&name, scope)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
}
|
||||
654
bindings/nodejs/src/user_model.rs
Normal file
654
bindings/nodejs/src/user_model.rs
Normal file
@@ -0,0 +1,654 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use napi::{self, JsUnknown, Result, bindgen_prelude::*};
|
||||
|
||||
use ironcalc::base::{
|
||||
BorderArea, ClipboardData, UserModel as BaseModel,
|
||||
expressions::types::Area,
|
||||
types::{CellType, Style},
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DefinedName {
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
}
|
||||
|
||||
fn to_js_error(error: String) -> Error {
|
||||
Error::new(Status::Unknown, error)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct UserModel {
|
||||
model: BaseModel,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl UserModel {
|
||||
#[napi(constructor)]
|
||||
pub fn new(name: String, locale: String, timezone: String) -> Result<Self> {
|
||||
let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?;
|
||||
Ok(Self { model })
|
||||
}
|
||||
|
||||
#[napi(factory)]
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<UserModel> {
|
||||
let model = BaseModel::from_bytes(bytes).map_err(to_js_error)?;
|
||||
Ok(UserModel { model })
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Result<()> {
|
||||
self.model.undo().map_err(to_js_error)
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Result<()> {
|
||||
self.model.redo().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "canUndo")]
|
||||
pub fn can_undo(&self) -> bool {
|
||||
self.model.can_undo()
|
||||
}
|
||||
|
||||
#[napi(js_name = "canRedo")]
|
||||
pub fn can_redo(&self) -> bool {
|
||||
self.model.can_redo()
|
||||
}
|
||||
|
||||
#[napi(js_name = "pauseEvaluation")]
|
||||
pub fn pause_evaluation(&mut self) {
|
||||
self.model.pause_evaluation()
|
||||
}
|
||||
|
||||
#[napi(js_name = "resumeEvaluation")]
|
||||
pub fn resume_evaluation(&mut self) {
|
||||
self.model.resume_evaluation()
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self) {
|
||||
self.model.evaluate();
|
||||
}
|
||||
|
||||
#[napi(js_name = "flushSendQueue")]
|
||||
pub fn flush_send_queue(&mut self) -> Vec<u8> {
|
||||
self.model.flush_send_queue()
|
||||
}
|
||||
|
||||
#[napi(js_name = "applyExternalDiffs")]
|
||||
pub fn apply_external_diffs(&mut self, diffs: &[u8]) -> Result<()> {
|
||||
self.model.apply_external_diffs(diffs).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellContent")]
|
||||
pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_cell_content(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "newSheet")]
|
||||
pub fn new_sheet(&mut self) -> Result<()> {
|
||||
self.model.new_sheet().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteSheet")]
|
||||
pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.delete_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "hideSheet")]
|
||||
pub fn hide_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.hide_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "unhideSheet")]
|
||||
pub fn unhide_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.unhide_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "renameSheet")]
|
||||
pub fn rename_sheet(&mut self, sheet: u32, name: String) -> Result<()> {
|
||||
self.model.rename_sheet(sheet, &name).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSheetColor")]
|
||||
pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_sheet_color(sheet, &color)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "rangeClearAll")]
|
||||
pub fn range_clear_all(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self.model.range_clear_all(&range).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "rangeClearContents")]
|
||||
pub fn range_clear_contents(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self.model.range_clear_contents(&range).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "rangeClearFormatting")]
|
||||
pub fn range_clear_formatting(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self
|
||||
.model
|
||||
.range_clear_formatting(&range)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "insertRow")]
|
||||
pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<()> {
|
||||
self.model.insert_row(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "insertColumn")]
|
||||
pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<()> {
|
||||
self.model.insert_column(sheet, column).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteRow")]
|
||||
pub fn delete_row(&mut self, sheet: u32, row: i32) -> Result<()> {
|
||||
self.model.delete_row(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteColumn")]
|
||||
pub fn delete_column(&mut self, sheet: u32, column: i32) -> Result<()> {
|
||||
self.model.delete_column(sheet, column).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setRowsHeight")]
|
||||
pub fn set_rows_height(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row_start: i32,
|
||||
row_end: i32,
|
||||
height: f64,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_rows_height(sheet, row_start, row_end, height)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setColumnsWidth")]
|
||||
pub fn set_columns_width(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
column_start: i32,
|
||||
column_end: i32,
|
||||
width: f64,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_columns_width(sheet, column_start, column_end, width)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getRowHeight")]
|
||||
pub fn get_row_height(&mut self, sheet: u32, row: i32) -> Result<f64> {
|
||||
self.model.get_row_height(sheet, row).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getColumnWidth")]
|
||||
pub fn get_column_width(&mut self, sheet: u32, column: i32) -> Result<f64> {
|
||||
self
|
||||
.model
|
||||
.get_column_width(sheet, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setUserInput")]
|
||||
pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, input: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_user_input(sheet, row, column, &input)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFormattedCellValue")]
|
||||
pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result<String> {
|
||||
self
|
||||
.model
|
||||
.get_formatted_cell_value(sheet, row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFrozenRowsCount")]
|
||||
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32> {
|
||||
self.model.get_frozen_rows_count(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getFrozenColumnsCount")]
|
||||
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32> {
|
||||
self
|
||||
.model
|
||||
.get_frozen_columns_count(sheet)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setFrozenRowsCount")]
|
||||
pub fn set_frozen_rows_count(&mut self, sheet: u32, count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_rows_count(sheet, count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setFrozenColumnsCount")]
|
||||
pub fn set_frozen_columns_count(&mut self, sheet: u32, count: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_frozen_columns_count(sheet, count)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "updateRangeStyle")]
|
||||
pub fn update_range_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
range: JsUnknown,
|
||||
style_path: String,
|
||||
value: String,
|
||||
) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(range)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.update_range_style(&range, &style_path, &value)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellStyle")]
|
||||
pub fn get_cell_style(
|
||||
&mut self,
|
||||
env: Env,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<JsUnknown> {
|
||||
let style = self
|
||||
.model
|
||||
.get_cell_style(sheet, row, column)
|
||||
.map_err(to_js_error)?;
|
||||
|
||||
env
|
||||
.to_js_value(&style)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPasteStyles")]
|
||||
pub fn on_paste_styles(&mut self, env: Env, styles: JsUnknown) -> Result<()> {
|
||||
let styles: &Vec<Vec<Style>> = &env
|
||||
.from_js_value(styles)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self.model.on_paste_styles(styles).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getCellType")]
|
||||
pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<i32> {
|
||||
Ok(
|
||||
match self
|
||||
.model
|
||||
.get_cell_type(sheet, row, column)
|
||||
.map_err(to_js_error)?
|
||||
{
|
||||
CellType::Number => 1,
|
||||
CellType::Text => 2,
|
||||
CellType::LogicalValue => 4,
|
||||
CellType::ErrorValue => 16,
|
||||
CellType::Array => 64,
|
||||
CellType::CompoundData => 128,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// I don't _think_ serializing to JsUnknown can't fail
|
||||
// FIXME: Remove this clippy directive
|
||||
#[napi(js_name = "getWorksheetsProperties")]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown {
|
||||
env
|
||||
.to_js_value(&self.model.get_worksheets_properties())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getSelectedSheet")]
|
||||
pub fn get_selected_sheet(&self) -> u32 {
|
||||
self.model.get_selected_sheet()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getSelectedCell")]
|
||||
pub fn get_selected_cell(&self) -> Vec<i32> {
|
||||
let (sheet, row, column) = self.model.get_selected_cell();
|
||||
vec![sheet as i32, row, column]
|
||||
}
|
||||
|
||||
// I don't _think_ serializing to JsUnknown can't fail
|
||||
// FIXME: Remove this clippy directive
|
||||
#[napi(js_name = "getSelectedView")]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn get_selected_view(&self, env: Env) -> JsUnknown {
|
||||
env.to_js_value(&self.model.get_selected_view()).unwrap()
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedSheet")]
|
||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<()> {
|
||||
self.model.set_selected_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedCell")]
|
||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_selected_cell(row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setSelectedRange")]
|
||||
pub fn set_selected_range(
|
||||
&mut self,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setTopLeftVisibleCell")]
|
||||
pub fn set_top_left_visible_cell(&mut self, top_row: i32, top_column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_top_left_visible_cell(top_row, top_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setShowGridLines")]
|
||||
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.set_show_grid_lines(sheet, show_grid_lines)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getShowGridLines")]
|
||||
pub fn get_show_grid_lines(&mut self, sheet: u32) -> Result<bool> {
|
||||
self.model.get_show_grid_lines(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "autoFillRows")]
|
||||
pub fn auto_fill_rows(&mut self, env: Env, source_area: JsUnknown, to_row: i32) -> Result<()> {
|
||||
let area: Area = env
|
||||
.from_js_value(source_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.auto_fill_rows(&area, to_row)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "autoFillColumns")]
|
||||
pub fn auto_fill_columns(
|
||||
&mut self,
|
||||
env: Env,
|
||||
source_area: JsUnknown,
|
||||
to_column: i32,
|
||||
) -> Result<()> {
|
||||
let area: Area = env
|
||||
.from_js_value(source_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.auto_fill_columns(&area, to_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowRight")]
|
||||
pub fn on_arrow_right(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_right().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowLeft")]
|
||||
pub fn on_arrow_left(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_left().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowUp")]
|
||||
pub fn on_arrow_up(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_up().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onArrowDown")]
|
||||
pub fn on_arrow_down(&mut self) -> Result<()> {
|
||||
self.model.on_arrow_down().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPageDown")]
|
||||
pub fn on_page_down(&mut self) -> Result<()> {
|
||||
self.model.on_page_down().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onPageUp")]
|
||||
pub fn on_page_up(&mut self) -> Result<()> {
|
||||
self.model.on_page_up().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setWindowWidth")]
|
||||
pub fn set_window_width(&mut self, window_width: f64) {
|
||||
self.model.set_window_width(window_width);
|
||||
}
|
||||
|
||||
#[napi(js_name = "setWindowHeight")]
|
||||
pub fn set_window_height(&mut self, window_height: f64) {
|
||||
self.model.set_window_height(window_height);
|
||||
}
|
||||
|
||||
#[napi(js_name = "getScrollX")]
|
||||
pub fn get_scroll_x(&self) -> Result<f64> {
|
||||
self.model.get_scroll_x().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "getScrollY")]
|
||||
pub fn get_scroll_y(&self) -> Result<f64> {
|
||||
self.model.get_scroll_y().map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onExpandSelectedRange")]
|
||||
pub fn on_expand_selected_range(&mut self, key: String) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.on_expand_selected_range(&key)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "onAreaSelecting")]
|
||||
pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.on_area_selecting(target_row, target_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[napi(js_name = "setAreaWithBorder")]
|
||||
pub fn set_area_with_border(
|
||||
&mut self,
|
||||
env: Env,
|
||||
area: JsUnknown,
|
||||
border_area: JsUnknown,
|
||||
) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
let border: BorderArea = env
|
||||
.from_js_value(border_area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.set_area_with_border(&range, &border)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi(js_name = "toBytes")]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.model.to_bytes()
|
||||
}
|
||||
|
||||
#[napi(js_name = "getName")]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.model.get_name()
|
||||
}
|
||||
|
||||
#[napi(js_name = "setName")]
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.model.set_name(&name);
|
||||
}
|
||||
|
||||
#[napi(js_name = "copyToClipboard")]
|
||||
pub fn copy_to_clipboard(&self, env: Env) -> Result<JsUnknown> {
|
||||
let data = self
|
||||
.model
|
||||
.copy_to_clipboard()
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
|
||||
env
|
||||
.to_js_value(&data)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "pasteFromClipboard")]
|
||||
pub fn paste_from_clipboard(
|
||||
&mut self,
|
||||
env: Env,
|
||||
source_sheet: u32,
|
||||
source_range: JsUnknown,
|
||||
clipboard: JsUnknown,
|
||||
is_cut: bool,
|
||||
) -> Result<()> {
|
||||
let source_range: (i32, i32, i32, i32) = env
|
||||
.from_js_value(source_range)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
let clipboard: ClipboardData = env
|
||||
.from_js_value(clipboard)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.paste_from_clipboard(source_sheet, source_range, &clipboard, is_cut)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "pasteCsvText")]
|
||||
pub fn paste_csv_string(&mut self, env: Env, area: JsUnknown, csv: String) -> Result<()> {
|
||||
let range: Area = env
|
||||
.from_js_value(area)
|
||||
.map_err(|e| to_js_error(e.to_string()))?;
|
||||
self
|
||||
.model
|
||||
.paste_csv_string(&range, &csv)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "getDefinedNameList")]
|
||||
pub fn get_defined_name_list(&self, env: Env) -> Result<JsUnknown> {
|
||||
let data: Vec<DefinedName> = self
|
||||
.model
|
||||
.get_defined_name_list()
|
||||
.iter()
|
||||
.map(|s| DefinedName {
|
||||
name: s.0.to_owned(),
|
||||
scope: s.1,
|
||||
formula: s.2.to_owned(),
|
||||
})
|
||||
.collect();
|
||||
env
|
||||
.to_js_value(&data)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "newDefinedName")]
|
||||
pub fn new_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.new_defined_name(&name, scope, &formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "updateDefinedName")]
|
||||
pub fn update_defined_name(
|
||||
&mut self,
|
||||
name: String,
|
||||
scope: Option<u32>,
|
||||
new_name: String,
|
||||
new_scope: Option<u32>,
|
||||
new_formula: String,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.update_defined_name(&name, scope, &new_name, new_scope, &new_formula)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi(js_name = "deleteDefinedName")]
|
||||
pub fn delete_definedname(&mut self, name: String, scope: Option<u32>) -> Result<()> {
|
||||
self
|
||||
.model
|
||||
.delete_defined_name(&name, scope)
|
||||
.map_err(|e| to_js_error(e.to_string()))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "pyroncalc"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
|
||||
[lib]
|
||||
|
||||
210
bindings/python/docs/api_reference.rst
Normal file
210
bindings/python/docs/api_reference.rst
Normal file
@@ -0,0 +1,210 @@
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
In general methods in IronCalc use a 0-index base for the the sheet index and 1-index base for the row and column indexes.
|
||||
|
||||
|
||||
.. method:: evaluate()
|
||||
|
||||
Evaluates the model. This needs to be done after each change, otherwise the model might be on a broken state.
|
||||
|
||||
.. method:: set_user_input(sheet: int, row: int, column: int, value: str)
|
||||
|
||||
Sets an input in a cell, as would be done by a user typing into a spreadsheet cell.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index (first row is 1).
|
||||
:param column: The 1-based column index (column “A” is 1).
|
||||
:param value: The value to set, e.g. ``"123"`` or ``"=A1*2"``.
|
||||
|
||||
.. method:: clear_cell_contents(sheet: int, row: int, column: int)
|
||||
|
||||
Removes the content of the cell but leaves the style intact.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index (first row is 1).
|
||||
:param column: The 1-based column index (column “A” is 1).
|
||||
|
||||
.. method:: get_cell_content(sheet: int, row: int, column: int) -> str
|
||||
|
||||
Returns the raw content of a cell. If the cell contains a formula,
|
||||
the returned string starts with ``"="``.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param column: The 1-based column index.
|
||||
:returns: The raw content, or an empty string if the cell is empty.
|
||||
|
||||
.. method:: get_cell_type(sheet: int, row: int, column: int) -> PyCellType
|
||||
|
||||
Returns the type of the cell (number, boolean, string, error, etc.).
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param column: The 1-based column index.
|
||||
:rtype: PyCellType
|
||||
|
||||
.. method:: get_formatted_cell_value(sheet: int, row: int, column: int) -> str
|
||||
|
||||
Returns the cell’s value as a formatted string, taking into
|
||||
account any number/currency/date formatting.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param column: The 1-based column index.
|
||||
:returns: Formatted string of the cell’s value.
|
||||
|
||||
.. method:: set_cell_style(sheet: int, row: int, column: int, style: PyStyle)
|
||||
|
||||
Sets the style of the cell at (sheet, row, column).
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param column: The 1-based column index.
|
||||
:param style: A PyStyle object specifying the style.
|
||||
|
||||
.. method:: get_cell_style(sheet: int, row: int, column: int) -> PyStyle
|
||||
|
||||
Retrieves the style of the specified cell.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param column: The 1-based column index.
|
||||
:returns: A PyStyle object describing the cell’s style.
|
||||
|
||||
.. method:: insert_rows(sheet: int, row: int, row_count: int)
|
||||
|
||||
Inserts new rows.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The position before which new rows are inserted (1-based).
|
||||
:param row_count: The number of rows to insert.
|
||||
|
||||
.. method:: insert_columns(sheet: int, column: int, column_count: int)
|
||||
|
||||
Inserts new columns.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param column: The position before which new columns are inserted (1-based).
|
||||
:param column_count: The number of columns to insert.
|
||||
|
||||
.. method:: delete_rows(sheet: int, row: int, row_count: int)
|
||||
|
||||
Deletes a range of rows.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The starting row to delete (1-based).
|
||||
:param row_count: How many rows to delete.
|
||||
|
||||
.. method:: delete_columns(sheet: int, column: int, column_count: int)
|
||||
|
||||
Deletes a range of columns.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param column: The starting column to delete (1-based).
|
||||
:param column_count: How many columns to delete.
|
||||
|
||||
.. method:: get_column_width(sheet: int, column: int) -> float
|
||||
|
||||
Retrieves the width of a given column.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param column: The 1-based column index.
|
||||
:rtype: float
|
||||
|
||||
.. method:: get_row_height(sheet: int, row: int) -> float
|
||||
|
||||
Retrieves the height of a given row.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:rtype: float
|
||||
|
||||
.. method:: set_column_width(sheet: int, column: int, width: float)
|
||||
|
||||
Sets the width of a given column.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param column: The 1-based column index.
|
||||
:param width: The desired width (float).
|
||||
|
||||
.. method:: set_row_height(sheet: int, row: int, height: float)
|
||||
|
||||
Sets the height of a given row.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row: The 1-based row index.
|
||||
:param height: The desired height (float).
|
||||
|
||||
.. method:: get_frozen_columns_count(sheet: int) -> int
|
||||
|
||||
Returns the number of columns frozen (pinned) on the left side of the sheet.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:rtype: int
|
||||
|
||||
.. method:: get_frozen_rows_count(sheet: int) -> int
|
||||
|
||||
Returns the number of rows frozen (pinned) at the top of the sheet.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:rtype: int
|
||||
|
||||
.. method:: set_frozen_columns_count(sheet: int, column_count: int)
|
||||
|
||||
Sets how many columns are frozen (pinned) on the left.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param column_count: The number of frozen columns (0-based).
|
||||
|
||||
.. method:: set_frozen_rows_count(sheet: int, row_count: int)
|
||||
|
||||
Sets how many rows are frozen (pinned) at the top.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param row_count: The number of frozen rows (0-based).
|
||||
|
||||
.. method:: get_worksheets_properties() -> List[PySheetProperty]
|
||||
|
||||
Returns a list of :class:`PySheetProperty` describing each worksheet’s
|
||||
name, visibility state, ID, and tab color.
|
||||
|
||||
:rtype: list of PySheetProperty
|
||||
|
||||
.. method:: set_sheet_color(sheet: int, color: str)
|
||||
|
||||
Sets the tab color of a sheet. Use an empty string to clear the color.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param color: A color in “#RRGGBB” format, or empty to remove color.
|
||||
|
||||
.. method:: add_sheet(sheet_name: str)
|
||||
|
||||
Creates a new sheet with the specified name.
|
||||
|
||||
:param sheet_name: The name to give the new sheet.
|
||||
|
||||
.. method:: new_sheet()
|
||||
|
||||
Creates a new sheet with an auto-generated name.
|
||||
|
||||
.. method:: delete_sheet(sheet: int)
|
||||
|
||||
Deletes the sheet at the given index.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
|
||||
.. method:: rename_sheet(sheet: int, new_name: str)
|
||||
|
||||
Renames the sheet at the given index.
|
||||
|
||||
:param sheet: The sheet index (0-based).
|
||||
:param new_name: The new sheet name.
|
||||
|
||||
.. method:: test_panic()
|
||||
|
||||
A test method that deliberately panics in Rust.
|
||||
Used for testing panic handling at the method level.
|
||||
|
||||
:raises WorkbookError: (wrapped Rust panic)
|
||||
@@ -1,13 +1,20 @@
|
||||
IronCalc: The democratization of spreadsheets
|
||||
=============================================
|
||||
IronCalc
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
IronCalc is a spreadsheet engine that allows you to create, modify and safe spreadsheets.
|
||||
installation
|
||||
usage_examples
|
||||
top_level_methods
|
||||
api_reference
|
||||
objects
|
||||
|
||||
IronCalc is a spreadsheet engine that allows you to create, modify and save spreadsheets.
|
||||
|
||||
A simple example that creates a model, sets a formula, evaluates it and gets the result back:
|
||||
|
||||
.. literalinclude:: examples/simple.py
|
||||
:language: python
|
||||
:language: python
|
||||
|
||||
|
||||
9
bindings/python/docs/installation.rst
Normal file
9
bindings/python/docs/installation.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
You can simply do:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install ironcalc
|
||||
|
||||
32
bindings/python/docs/objects.rst
Normal file
32
bindings/python/docs/objects.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
Objects
|
||||
-------
|
||||
|
||||
The following examples
|
||||
|
||||
|
||||
``WorkbookError``
|
||||
^^^^^^^^^^^^^^^^^
|
||||
Exceptions of type ``WorkbookError`` are raised whenever there is a problem with
|
||||
the workbook (e.g., invalid parameters, file I/O error, or even a Rust panic).
|
||||
You can catch these exceptions in Python as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ironcalc import WorkbookError
|
||||
|
||||
try:
|
||||
# Some operation on PyModel
|
||||
pass
|
||||
except WorkbookError as e:
|
||||
print("Caught a workbook error:", e)
|
||||
|
||||
``PyCellType``
|
||||
^^^^^^^^^^^^^^
|
||||
Represents the type of a cell (e.g., number, string, boolean, etc.). You can
|
||||
check the type of a cell with :meth:`PyModel.get_cell_type`.
|
||||
|
||||
``PyStyle``
|
||||
^^^^^^^^^^^
|
||||
Represents the style of a cell (font, bold, number formats, alignment, etc.).
|
||||
You can get/set these styles with :meth:`PyModel.get_cell_style`
|
||||
and :meth:`PyModel.set_cell_style`.
|
||||
6
bindings/python/docs/top_level_methods.rst
Normal file
6
bindings/python/docs/top_level_methods.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
Top Level Methods
|
||||
-----------------
|
||||
|
||||
.. autofunction:: ironcalc.create
|
||||
.. autofunction:: ironcalc.load_from_xlsx
|
||||
.. autofunction:: ironcalc.load_from_icalc
|
||||
37
bindings/python/docs/usage_examples.rst
Normal file
37
bindings/python/docs/usage_examples.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Usage Examples
|
||||
--------------
|
||||
|
||||
Creating an Empty Model
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ironcalc as ic
|
||||
|
||||
model = ic.create("My Workbook", "en", "UTC")
|
||||
|
||||
Loading from XLSX
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import ironcalc as ic
|
||||
|
||||
model = ic.load_from_xlsx("example.xlsx", "en", "UTC")
|
||||
|
||||
Modifying and Saving
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
model = ic.create("model", "en", "UTC")
|
||||
model.set_user_input(0, 1, 1, "123")
|
||||
model.set_user_input(0, 1, 2, "=A1*2")
|
||||
model.evaluate()
|
||||
|
||||
# Save to XLSX
|
||||
model.save_to_xlsx("updated.xlsx")
|
||||
|
||||
# Or save to the binary format
|
||||
model.save_to_icalc("my_workbook.icalc")
|
||||
@@ -16,7 +16,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Office/Business :: Financial :: Spreadsheet",
|
||||
"Topic :: Office/Business :: Financial :: Spreadsheet",
|
||||
]
|
||||
authors = [
|
||||
{ name = "Nicolás Hatcher", email = "nicolas@theuniverse.today" },
|
||||
|
||||
@@ -2,8 +2,8 @@ use pyo3::exceptions::PyException;
|
||||
use pyo3::{create_exception, prelude::*, wrap_pyfunction};
|
||||
|
||||
use types::{PySheetProperty, PyStyle};
|
||||
use xlsx::base::types::Style;
|
||||
use xlsx::base::Model;
|
||||
use xlsx::base::types::Style;
|
||||
|
||||
use xlsx::export::{save_to_icalc, save_to_xlsx};
|
||||
use xlsx::import;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "wasm"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
authors = ["Nicolas Hatcher <nicolas@theuniverse.today>"]
|
||||
description = "IronCalc Web bindings"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/ironcalc/ironcalc"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -7,6 +7,15 @@ https://www.npmjs.com/package/@ironcalc/wasm?activeTab=readme
|
||||
|
||||
## Building
|
||||
|
||||
Dependencies:
|
||||
|
||||
* Rust
|
||||
* wasm-pack
|
||||
* TypeScript
|
||||
* Python
|
||||
* binutils (for make)
|
||||
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::{
|
||||
prelude::{wasm_bindgen, JsError},
|
||||
JsValue,
|
||||
prelude::{JsError, wasm_bindgen},
|
||||
};
|
||||
|
||||
use ironcalc_base::{
|
||||
BorderArea, ClipboardData, UserModel as BaseModel,
|
||||
expressions::{lexer::util::get_tokens as tokenizer, types::Area, utils::number_to_column},
|
||||
types::{CellType, Style},
|
||||
BorderArea, ClipboardData, UserModel as BaseModel,
|
||||
};
|
||||
|
||||
fn to_js_error(error: String) -> JsError {
|
||||
@@ -174,6 +174,27 @@ impl Model {
|
||||
self.model.range_clear_contents(&range).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "rangeClearFormatting")]
|
||||
pub fn range_clear_formatting(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<(), JsError> {
|
||||
let range = Area {
|
||||
sheet,
|
||||
row: start_row,
|
||||
column: start_column,
|
||||
width: end_column - start_column + 1,
|
||||
height: end_row - start_row + 1,
|
||||
};
|
||||
self.model
|
||||
.range_clear_formatting(&range)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "insertRow")]
|
||||
pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<(), JsError> {
|
||||
self.model.insert_row(sheet, row).map_err(to_js_error)
|
||||
@@ -194,17 +215,29 @@ impl Model {
|
||||
self.model.delete_column(sheet, column).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setRowHeight")]
|
||||
pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<(), JsError> {
|
||||
#[wasm_bindgen(js_name = "setRowsHeight")]
|
||||
pub fn set_rows_height(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row_start: i32,
|
||||
row_end: i32,
|
||||
height: f64,
|
||||
) -> Result<(), JsError> {
|
||||
self.model
|
||||
.set_row_height(sheet, row, height)
|
||||
.set_rows_height(sheet, row_start, row_end, height)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setColumnWidth")]
|
||||
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), JsError> {
|
||||
#[wasm_bindgen(js_name = "setColumnsWidth")]
|
||||
pub fn set_columns_width(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
column_start: i32,
|
||||
column_end: i32,
|
||||
width: f64,
|
||||
) -> Result<(), JsError> {
|
||||
self.model
|
||||
.set_column_width(sheet, column, width)
|
||||
.set_columns_width(sheet, column_start, column_end, width)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
@@ -271,6 +304,37 @@ impl Model {
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
// This two are only used when we want to compute the automatic width of a column or height of a row
|
||||
#[wasm_bindgen(js_name = "getRowsWithData")]
|
||||
pub fn get_rows_with_data(&self, sheet: u32, column: i32) -> Result<Vec<i32>, JsError> {
|
||||
let sheet_data = &self
|
||||
.model
|
||||
.get_model()
|
||||
.workbook
|
||||
.worksheet(sheet)
|
||||
.map_err(to_js_error)?
|
||||
.sheet_data;
|
||||
Ok(sheet_data
|
||||
.iter()
|
||||
.filter(|(_, data)| data.contains_key(&column))
|
||||
.map(|(row, _)| *row)
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getColumnsWithData")]
|
||||
pub fn get_columns_with_data(&self, sheet: u32, row: i32) -> Result<Vec<i32>, JsError> {
|
||||
Ok(self
|
||||
.model
|
||||
.get_model()
|
||||
.workbook
|
||||
.worksheet(sheet)
|
||||
.map_err(to_js_error)?
|
||||
.sheet_data
|
||||
.get(&row)
|
||||
.map(|row_data| row_data.keys().copied().collect())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "updateRangeStyle")]
|
||||
pub fn update_range_style(
|
||||
&mut self,
|
||||
|
||||
@@ -20,7 +20,7 @@ test('Row height', () => {
|
||||
let model = new Model('Workbook1', 'en', 'UTC');
|
||||
assert.strictEqual(model.getRowHeight(0, 3), DEFAULT_ROW_HEIGHT);
|
||||
|
||||
model.setRowHeight(0, 3, 32);
|
||||
model.setRowsHeight(0, 3, 3, 32);
|
||||
assert.strictEqual(model.getRowHeight(0, 3), 32);
|
||||
|
||||
model.undo();
|
||||
@@ -29,7 +29,7 @@ test('Row height', () => {
|
||||
model.redo();
|
||||
assert.strictEqual(model.getRowHeight(0, 3), 32);
|
||||
|
||||
model.setRowHeight(0, 3, 320);
|
||||
model.setRowsHeight(0, 3, 3, 320);
|
||||
assert.strictEqual(model.getRowHeight(0, 3), 320);
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ test("Add sheets", (t) => {
|
||||
test("invalid sheet index throws an exception", () => {
|
||||
const model = new Model('Workbook1', 'en', 'UTC');
|
||||
assert.throws(() => {
|
||||
model.setRowHeight(1, 1, 100);
|
||||
model.setRowsHeight(1, 1, 1, 100);
|
||||
}, {
|
||||
name: 'Error',
|
||||
message: 'Invalid sheet index',
|
||||
@@ -106,7 +106,7 @@ test("invalid sheet index throws an exception", () => {
|
||||
test("invalid column throws an exception", () => {
|
||||
const model = new Model('Workbook1', 'en', 'UTC');
|
||||
assert.throws(() => {
|
||||
model.setRowHeight(0, -1, 100);
|
||||
model.setRowsHeight(0, -1, 0, 100);
|
||||
}, {
|
||||
name: 'Error',
|
||||
message: "Row number '-1' is not valid.",
|
||||
@@ -115,7 +115,7 @@ test("invalid column throws an exception", () => {
|
||||
|
||||
test("floating column numbers get truncated", () => {
|
||||
const model = new Model('Workbook1', 'en', 'UTC');
|
||||
model.setRowHeight(0.8, 5.2, 100.5);
|
||||
model.setRowsHeight(0.8, 5.2, 5.5, 100.5);
|
||||
|
||||
assert.strictEqual(model.getRowHeight(0.11, 5.99), 100.5);
|
||||
assert.strictEqual(model.getRowHeight(0, 5), 100.5);
|
||||
|
||||
@@ -6,4 +6,4 @@ lang: en-US
|
||||
|
||||
# How to Contribute
|
||||
|
||||
If you want to give us a hand, take a look at our [GitHub repository](https://github.com/ironcalc/IronCalc?tab=readme-ov-file#collaborators-needed-call-to-action), join our [Discord Channel](https://discord.gg/sjaefMWE) or send us an email to [hello@ironcalc.com](mailto:hello@ironcalc.com).
|
||||
If you want to give us a hand, take a look at our [GitHub repository](https://github.com/ironcalc/IronCalc?tab=readme-ov-file#collaborators-needed-call-to-action), join our [Discord Channel](https://discord.com/invite/zZYWfh3RHJ) or send us an email to [hello@ironcalc.com](mailto:hello@ironcalc.com).
|
||||
|
||||
@@ -12,10 +12,6 @@ Although IronCalc is ready for use, it’s important to understand its current l
|
||||
|
||||
IronCalc currently does not implement arrays or array formulas. These are planned and are coming very soon, as they are the highest priority on the engine side.
|
||||
|
||||
## **Name Manager** <Badge type="info" text="Planned" />
|
||||
|
||||
While IronCalc supports importing and exporting defined names, it does not yet allow you to create, delete, or update them in the UI. This feature is expected to be implemented shortly.
|
||||
|
||||
## **Only English Supported**
|
||||
|
||||
The MVP version of IronCalc supports only the English language. However, version 1.0 will include support for three languages: **English**, **German**, and **Spanish**.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user