Compare commits
1 Commits
v0.2.0
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afecf29356 |
18
.github/workflows/publish-wiki.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Publish wiki
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- wiki/**
|
|
||||||
- .github/workflows/publish-wiki.yml
|
|
||||||
concurrency:
|
|
||||||
group: publish-wiki
|
|
||||||
cancel-in-progress: true
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
jobs:
|
|
||||||
publish-wiki:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: Andrew-Chen-Wang/github-wiki-action@v4
|
|
||||||
3
.github/workflows/rust-build-test.yaml
vendored
@@ -16,9 +16,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install wasm-pack
|
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/test-coverage.yaml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Coverage
|
name: Coverage
|
||||||
|
|
||||||
on: [pull_request]
|
on: [pull_request, push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
coverage:
|
coverage:
|
||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Install cargo-llvm-cov
|
- name: Install cargo-llvm-cov
|
||||||
uses: taiki-e/install-action@cargo-llvm-cov
|
uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
- name: Generate code coverage
|
- name: Generate code coverage
|
||||||
run: cargo llvm-cov --all-features --workspace --exclude pyroncalc --exclude wasm --lcov --output-path lcov.info
|
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
target/*
|
target/*
|
||||||
.DS_Store
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# .readthedocs.yaml
|
|
||||||
# Read the Docs configuration file
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the OS, Python version and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-22.04
|
|
||||||
tools:
|
|
||||||
python: "3.12"
|
|
||||||
# You can also specify other tool versions:
|
|
||||||
# nodejs: "19"
|
|
||||||
# rust: "1.64"
|
|
||||||
# golang: "1.19"
|
|
||||||
|
|
||||||
# Build documentation in the "docs/" directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: bindings/python/docs/conf.py
|
|
||||||
|
|
||||||
# Optionally build your docs in additional formats such as PDF and ePub
|
|
||||||
# formats:
|
|
||||||
# - pdf
|
|
||||||
# - epub
|
|
||||||
|
|
||||||
# Optional but recommended, declare the Python requirements required
|
|
||||||
# to build your documentation
|
|
||||||
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
|
||||||
# python:
|
|
||||||
# install:
|
|
||||||
# - requirements: docs/requirements.txt
|
|
||||||
15
CHANGELOG.md
@@ -1,15 +0,0 @@
|
|||||||
# CHANGELOG
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## [0.2.0] - 2024-11-06 (The HN release)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Rust crate ironcalc_base
|
|
||||||
- Rust crate ironcalc
|
|
||||||
- Minimal Python bindings (only Linux)
|
|
||||||
- JavaScript bindings
|
|
||||||
- React WebApp
|
|
||||||
|
|
||||||
[0.2.0]: https://github.com/IronCalc/ironcalc/releases/tag/v0.2.0
|
|
||||||
821
Cargo.lock
generated
@@ -4,8 +4,6 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"base",
|
"base",
|
||||||
"xlsx",
|
"xlsx",
|
||||||
"bindings/wasm",
|
|
||||||
"bindings/python",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|||||||
32
Makefile
@@ -1,53 +1,33 @@
|
|||||||
.PHONY: lint
|
|
||||||
lint:
|
lint:
|
||||||
cargo fmt -- --check
|
cargo fmt -- --check
|
||||||
cargo clippy --all-targets --all-features
|
cargo clippy --all-targets --all-features
|
||||||
cd webapp && npm install && npm run check
|
|
||||||
|
|
||||||
.PHONY: format
|
|
||||||
format:
|
format:
|
||||||
cargo fmt
|
cargo fmt
|
||||||
|
|
||||||
.PHONY: tests
|
|
||||||
tests: lint
|
tests: lint
|
||||||
cargo test
|
cargo test
|
||||||
./target/debug/documentation
|
make remove-xlsx
|
||||||
cmp functions.md wiki/functions.md || exit 1
|
|
||||||
make remove-artifacts
|
|
||||||
# Regretabbly we need to build the wasm twice, once for the nodejs tests
|
|
||||||
# and a second one for the vitest.
|
|
||||||
cd bindings/wasm/ && wasm-pack build --target nodejs && node tests/test.mjs && make
|
|
||||||
cd webapp && npm run test
|
|
||||||
cd bindings/python && ./run_tests.sh && ./run_examples.sh
|
|
||||||
|
|
||||||
.PHONY: remove-artifacts
|
remove-xlsx:
|
||||||
remove-artifacts:
|
|
||||||
rm -f xlsx/hello-calc.xlsx
|
rm -f xlsx/hello-calc.xlsx
|
||||||
rm -f xlsx/hello-styles.xlsx
|
rm -f xlsx/hello-styles.xlsx
|
||||||
rm -f xlsx/widths-and-heights.xlsx
|
rm -f xlsx/widths-and-heights.xlsx
|
||||||
rm -f functions.md
|
|
||||||
|
|
||||||
.PHONY: clean
|
clean: remove-xlsx
|
||||||
clean: remove-artifacts
|
|
||||||
cargo clean
|
cargo clean
|
||||||
rm -r -f base/target
|
rm -r -f base/target
|
||||||
rm -r -f xlsx/target
|
rm -r -f xlsx/target
|
||||||
rm -r -f bindings/python/target
|
|
||||||
rm -r -f bindings/wasm/targets
|
|
||||||
rm -f cargo-test-*
|
rm -f cargo-test-*
|
||||||
rm -f base/cargo-test-*
|
rm -f base/cargo-test-*
|
||||||
rm -f xlsx/cargo-test-*
|
rm -f xlsx/cargo-test-*
|
||||||
|
|
||||||
.PHONY: coverage
|
|
||||||
coverage:
|
coverage:
|
||||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-C instrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
|
CARGO_INCREMENTAL=0 RUSTFLAGS='-C instrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
|
||||||
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
||||||
|
|
||||||
.PHONY: update-docs
|
|
||||||
update-docs:
|
|
||||||
cargo build
|
|
||||||
./target/debug/documentation -o wiki/functions.md
|
|
||||||
|
|
||||||
.PHONY: docs
|
|
||||||
docs:
|
docs:
|
||||||
cargo doc --no-deps
|
cargo doc --no-deps
|
||||||
|
|
||||||
|
.PHONY: lint format tests docs coverage all
|
||||||
@@ -123,12 +123,12 @@ See https://github.com/ironcalc
|
|||||||
|
|
||||||
An early preview of the technology running entirely in your browser:
|
An early preview of the technology running entirely in your browser:
|
||||||
|
|
||||||
https://app.ironcalc.com
|
https://playground.ironcalc.com
|
||||||
|
|
||||||
|
|
||||||
# Collaborators needed!. Call to action
|
# Collaborators needed!. Call to action
|
||||||
|
|
||||||
We don't have a vibrant community just yet. This is the very stages of the project. But if you are passionate about code with high standards and no compromises, if you are looking for a project with high impact, if you are interested in a better, more open infrastructure for spreadsheets, whether you are a developer (rust, python, TypeScript, electron/tauri/anything else native app, React, you name it), a designer, an Excel power user who wants features, a business looking to integrate a MIT/Apache licensed spreadsheet in your own SaaS application join us!
|
We don't have a vibrant community just yet. This is the very stages of the project. But if you are passionate about code with high standards and no compromises, if you are looking for a project with high impact, if you are interested in a better, more open infrastructure for spreadsheets, whether you are a developer (rust, python, TypeScript, electron/tauri/anything else native app, React, you name it), a designer (we need a logo desperately!), an Excel power user who wants features, a business looking to integrate a MIT/Apache licensed spreadsheet in your own SaaS application join us!
|
||||||
|
|
||||||
The best place to start will be to join or [discord channel](https://discord.gg/zZYWfh3RHJ) or send us an email at hello@ironcalc.com.
|
The best place to start will be to join or [discord channel](https://discord.gg/zZYWfh3RHJ) or send us an email at hello@ironcalc.com.
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,8 +0,0 @@
|
|||||||
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="600" height="600" rx="20" fill="#F2994A"/>
|
|
||||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 100C348.98 166.034 322.748 229.362 276.055 276.055C268.163 283.947 259.796 291.255 251.021 297.95L251.021 500L348.98 500H251.021C251.021 433.966 277.252 370.637 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05L348.98 100Z" fill="white"/>
|
|
||||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M251.021 100.068C251.003 140.096 235.094 178.481 206.788 206.787C178.466 235.109 140.053 251.02 100 251.02V348.979C154.873 348.979 207.877 330.866 251.021 297.95V100.068Z" fill="white"/>
|
|
||||||
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 499.882C349.011 459.872 364.918 421.507 393.213 393.213C421.534 364.891 459.947 348.98 500 348.98V251.02C445.128 251.02 392.123 269.134 348.98 302.05V499.882Z" fill="white"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.055 276.055C322.748 229.362 348.98 166.034 348.98 100H251.021V297.95C259.796 291.255 268.163 283.947 276.055 276.055Z" fill="white"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M348.98 302.05V499.895C348.98 499.93 348.98 499.965 348.98 500L251.021 500C251.021 499.946 251.02 499.891 251.021 499.837C251.064 433.862 277.291 370.599 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
assets/logo.png
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironcalc_base"
|
name = "ironcalc_base"
|
||||||
version = "0.2.0"
|
version = "0.1.2"
|
||||||
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://www.ironcalc.com"
|
homepage = "https://www.ironcalc.com"
|
||||||
@@ -12,22 +12,19 @@ readme = "README.md"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_repr = "0.1"
|
||||||
ryu = "1.0"
|
ryu = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
chrono-tz = "0.9"
|
chrono-tz = "0.7.0"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
bitcode = "0.6.0"
|
bincode = "=2.0.0-rc.3"
|
||||||
csv = "1.3.0"
|
|
||||||
csv-sniffer = "0.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
serde_json = "1.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = { version = "0.3.69" }
|
js-sys = { version = "0.3.60" }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.4"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use ironcalc_base::{types::CellType, Model};
|
use ironcalc_base::{model::Model, types::CellType};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
||||||
// A1
|
// A1
|
||||||
model.set_user_input(0, 1, 1, "1".to_string())?;
|
model.set_user_input(0, 1, 1, "1".to_string());
|
||||||
// A2
|
// A2
|
||||||
model.set_user_input(0, 2, 1, "2".to_string())?;
|
model.set_user_input(0, 2, 1, "2".to_string());
|
||||||
// A3
|
// A3
|
||||||
model.set_user_input(0, 3, 1, "3".to_string())?;
|
model.set_user_input(0, 3, 1, "3".to_string());
|
||||||
// B1
|
// B1
|
||||||
model.set_user_input(0, 1, 2, "=SUM(A1:A3)".to_string())?;
|
model.set_user_input(0, 1, 2, "=SUM(A1:A3)".to_string());
|
||||||
// B2
|
// B2
|
||||||
model.set_user_input(0, 2, 2, "=B1/0".to_string())?;
|
model.set_user_input(0, 2, 2, "=B1/0".to_string());
|
||||||
// Evaluate
|
// Evaluate
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use ironcalc_base::{cell::CellValue, Model};
|
use ironcalc_base::{cell::CellValue, model::Model};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
||||||
// A1
|
// A1
|
||||||
model.set_user_input(0, 1, 1, "Hello".to_string())?;
|
model.set_user_input(0, 1, 1, "Hello".to_string());
|
||||||
// B1
|
// B1
|
||||||
model.set_user_input(0, 1, 2, "world!".to_string())?;
|
model.set_user_input(0, 1, 2, "world!".to_string());
|
||||||
// C1
|
// C1
|
||||||
model.set_user_input(0, 1, 3, "=CONCAT(A1, \" \", B1".to_string())?;
|
model.set_user_input(0, 1, 3, "=CONCAT(A1, \" \", B1".to_string());
|
||||||
// evaluates
|
// evaluates
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
|
|||||||
@@ -77,13 +77,14 @@ impl Model {
|
|||||||
let style = source_cell.get_style();
|
let style = source_cell.get_style();
|
||||||
// FIXME: we need some user_input getter instead of get_text
|
// FIXME: we need some user_input getter instead of get_text
|
||||||
let formula_or_value = self
|
let formula_or_value = self
|
||||||
.get_cell_formula(sheet, source_row, source_column)?
|
.cell_formula(sheet, source_row, source_column)?
|
||||||
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
|
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
|
||||||
self.set_user_input(sheet, target_row, target_column, formula_or_value)?;
|
self.set_user_input(sheet, target_row, target_column, formula_or_value);
|
||||||
self.workbook
|
self.workbook
|
||||||
.worksheet_mut(sheet)?
|
.worksheet_mut(sheet)?
|
||||||
.set_cell_style(target_row, target_column, style)?;
|
.set_cell_style(target_row, target_column, style);
|
||||||
self.cell_clear_all(sheet, source_row, source_column)
|
self.delete_cell(sheet, source_row, source_column)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts one or more new columns into the model at the specified index.
|
/// Inserts one or more new columns into the model at the specified index.
|
||||||
@@ -156,11 +157,6 @@ impl Model {
|
|||||||
return Err("Please use insert columns instead".to_string());
|
return Err("Please use insert columns instead".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// first column being deleted
|
|
||||||
let column_start = column;
|
|
||||||
// last column being deleted
|
|
||||||
let column_end = column + column_count - 1;
|
|
||||||
|
|
||||||
// Move cells
|
// Move cells
|
||||||
let worksheet = &self.workbook.worksheet(sheet)?;
|
let worksheet = &self.workbook.worksheet(sheet)?;
|
||||||
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
|
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
|
||||||
@@ -170,11 +166,11 @@ impl Model {
|
|||||||
for r in all_rows {
|
for r in all_rows {
|
||||||
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
|
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
|
||||||
for col in columns {
|
for col in columns {
|
||||||
if col >= column_start {
|
if col >= column {
|
||||||
if col > column_end {
|
if col >= column + column_count {
|
||||||
self.move_cell(sheet, r, col, r, col - column_count)?;
|
self.move_cell(sheet, r, col, r, col - column_count)?;
|
||||||
} else {
|
} else {
|
||||||
self.cell_clear_all(sheet, r, col)?;
|
self.delete_cell(sheet, r, col)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,64 +184,6 @@ impl Model {
|
|||||||
delta: -column_count,
|
delta: -column_count,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
|
||||||
|
|
||||||
// deletes all the column styles
|
|
||||||
let mut new_columns = Vec::new();
|
|
||||||
for col in worksheet.cols.iter_mut() {
|
|
||||||
// range under study
|
|
||||||
let min = col.min;
|
|
||||||
let max = col.max;
|
|
||||||
// In the diagram:
|
|
||||||
// |xxxxx| range we are studying [min, max]
|
|
||||||
// |*****| range we are deleting [column_start, column_end]
|
|
||||||
// we are going to split it in three big cases:
|
|
||||||
// ----------------|xxxxxxxx|-----------------
|
|
||||||
// -----|*****|------------------------------- Case A
|
|
||||||
// -------|**********|------------------------ Case B
|
|
||||||
// -------------|**************|-------------- Case C
|
|
||||||
// ------------------|****|------------------- Case D
|
|
||||||
// ---------------------|**********|---------- Case E
|
|
||||||
// -----------------------------|*****|------- Case F
|
|
||||||
if column_start < min {
|
|
||||||
if column_end < min {
|
|
||||||
// Case A
|
|
||||||
// We displace all columns
|
|
||||||
let mut new_column = col.clone();
|
|
||||||
new_column.min = min - column_count;
|
|
||||||
new_column.max = max - column_count;
|
|
||||||
new_columns.push(new_column);
|
|
||||||
} else if column_end < max {
|
|
||||||
// Case B
|
|
||||||
// We displace the end
|
|
||||||
let mut new_column = col.clone();
|
|
||||||
new_column.min = column_start;
|
|
||||||
new_column.max = max - column_count;
|
|
||||||
new_columns.push(new_column);
|
|
||||||
} else {
|
|
||||||
// Case C
|
|
||||||
// skip this, we are deleting the whole range
|
|
||||||
}
|
|
||||||
} else if column_start <= max {
|
|
||||||
if column_end <= max {
|
|
||||||
// Case D
|
|
||||||
// We displace the end
|
|
||||||
let mut new_column = col.clone();
|
|
||||||
new_column.max = max - column_count;
|
|
||||||
new_columns.push(new_column);
|
|
||||||
} else {
|
|
||||||
// Case E
|
|
||||||
let mut new_column = col.clone();
|
|
||||||
new_column.max = column_start - 1;
|
|
||||||
new_columns.push(new_column);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Case F
|
|
||||||
// No action required
|
|
||||||
new_columns.push(col.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
worksheet.cols = new_columns;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -345,7 +283,7 @@ impl Model {
|
|||||||
// remove all cells in row
|
// remove all cells in row
|
||||||
// FIXME: We could just remove the entire row in one go
|
// FIXME: We could just remove the entire row in one go
|
||||||
for column in columns {
|
for column in columns {
|
||||||
self.cell_clear_all(sheet, r, column)?;
|
self.delete_cell(sheet, r, column)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
/// A CellValue is the representation of the cell content.
|
/// A CellValue is the representation of the cell content.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum CellValue {
|
pub enum CellValue {
|
||||||
None,
|
None,
|
||||||
String(String),
|
String(String),
|
||||||
@@ -11,6 +14,17 @@ pub enum CellValue {
|
|||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CellValue {
|
||||||
|
pub fn to_json_str(&self) -> String {
|
||||||
|
match &self {
|
||||||
|
CellValue::None => "null".to_string(),
|
||||||
|
CellValue::String(s) => json!(s).to_string(),
|
||||||
|
CellValue::Number(f) => json!(f).to_string(),
|
||||||
|
CellValue::Boolean(b) => json!(b).to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<f64> for CellValue {
|
impl From<f64> for CellValue {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
Self::Number(value)
|
Self::Number(value)
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
/// COLUMN_WIDTH and ROW_HEIGHT are pixel values
|
/// COLUMN_WIDTH and ROW_HEIGHT are pixel values
|
||||||
/// A column width of Excel value `w` will result in `w * COLUMN_WIDTH_FACTOR` pixels
|
/// A column width of Excel value `w` will result in `w * COLUMN_WIDTH_FACTOR` pixels
|
||||||
/// Note that these constants are inlined
|
/// Note that these constants are inlined
|
||||||
pub(crate) const DEFAULT_COLUMN_WIDTH: f64 = 125.0;
|
pub(crate) const DEFAULT_COLUMN_WIDTH: f64 = 100.0;
|
||||||
pub(crate) const DEFAULT_ROW_HEIGHT: f64 = 28.0;
|
pub(crate) const DEFAULT_ROW_HEIGHT: f64 = 21.0;
|
||||||
pub(crate) const COLUMN_WIDTH_FACTOR: f64 = 12.0;
|
pub(crate) const COLUMN_WIDTH_FACTOR: f64 = 12.0;
|
||||||
pub(crate) const ROW_HEIGHT_FACTOR: f64 = 2.0;
|
pub(crate) const ROW_HEIGHT_FACTOR: f64 = 2.0;
|
||||||
pub(crate) const DEFAULT_WINDOW_HEIGH: i64 = 600;
|
|
||||||
pub(crate) const DEFAULT_WINDOW_WIDTH: i64 = 800;
|
|
||||||
|
|
||||||
pub(crate) const LAST_COLUMN: i32 = 16_384;
|
pub(crate) const LAST_COLUMN: i32 = 16_384;
|
||||||
pub(crate) const LAST_ROW: i32 = 1_048_576;
|
pub(crate) const LAST_ROW: i32 = 1_048_576;
|
||||||
|
|||||||
@@ -308,9 +308,9 @@ impl Lexer {
|
|||||||
return self.consume_range(None);
|
return self.consume_range(None);
|
||||||
}
|
}
|
||||||
let name_upper = name.to_ascii_uppercase();
|
let name_upper = name.to_ascii_uppercase();
|
||||||
if name_upper == self.language.booleans.r#true {
|
if name_upper == self.language.booleans.true_value {
|
||||||
return TokenType::Boolean(true);
|
return TokenType::Boolean(true);
|
||||||
} else if name_upper == self.language.booleans.r#false {
|
} else if name_upper == self.language.booleans.false_value {
|
||||||
return TokenType::Boolean(false);
|
return TokenType::Boolean(false);
|
||||||
}
|
}
|
||||||
if self.mode == LexerMode::A1 {
|
if self.mode == LexerMode::A1 {
|
||||||
@@ -660,8 +660,8 @@ impl Lexer {
|
|||||||
fn consume_error(&mut self) -> TokenType {
|
fn consume_error(&mut self) -> TokenType {
|
||||||
let errors = &self.language.errors;
|
let errors = &self.language.errors;
|
||||||
let rest_of_formula: String = self.chars[self.position - 1..self.len].iter().collect();
|
let rest_of_formula: String = self.chars[self.position - 1..self.len].iter().collect();
|
||||||
if rest_of_formula.starts_with(&errors.r#ref) {
|
if rest_of_formula.starts_with(&errors.ref_value) {
|
||||||
self.position += errors.r#ref.chars().count() - 1;
|
self.position += errors.ref_value.chars().count() - 1;
|
||||||
return TokenType::Error(Error::REF);
|
return TokenType::Error(Error::REF);
|
||||||
} else if rest_of_formula.starts_with(&errors.name) {
|
} else if rest_of_formula.starts_with(&errors.name) {
|
||||||
self.position += errors.name.chars().count() - 1;
|
self.position += errors.name.chars().count() - 1;
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use crate::{
|
|||||||
token::TokenType,
|
token::TokenType,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale,
|
locale::get_locale_fix,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn new_language_lexer(formula: &str, locale: &str, language: &str) -> Lexer {
|
fn new_language_lexer(formula: &str, locale: &str, language: &str) -> Lexer {
|
||||||
let locale = get_locale(locale).unwrap();
|
let locale = get_locale_fix(locale).unwrap();
|
||||||
let language = get_language(language).unwrap();
|
let language = get_language(language).unwrap();
|
||||||
Lexer::new(formula, LexerMode::A1, locale, language)
|
Lexer::new(formula, LexerMode::A1, locale, language)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ impl Parser {
|
|||||||
|
|
||||||
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
||||||
self.lexer.set_formula(formula);
|
self.lexer.set_formula(formula);
|
||||||
self.context.clone_from(context);
|
self.context = context.clone();
|
||||||
self.parse_expr()
|
self.parse_expr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
use crate::language::Language;
|
use crate::language::Language;
|
||||||
|
|
||||||
@@ -80,7 +81,8 @@ impl fmt::Display for OpProduct {
|
|||||||
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
||||||
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
||||||
/// Note that they are serialized/deserialized by index
|
/// Note that they are serialized/deserialized by index
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize_repr, Deserialize_repr, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
REF,
|
REF,
|
||||||
NAME,
|
NAME,
|
||||||
@@ -118,7 +120,7 @@ impl Error {
|
|||||||
pub fn to_localized_error_string(&self, language: &Language) -> String {
|
pub fn to_localized_error_string(&self, language: &Language) -> String {
|
||||||
match self {
|
match self {
|
||||||
Error::NULL => language.errors.null.to_string(),
|
Error::NULL => language.errors.null.to_string(),
|
||||||
Error::REF => language.errors.r#ref.to_string(),
|
Error::REF => language.errors.ref_value.to_string(),
|
||||||
Error::NAME => language.errors.name.to_string(),
|
Error::NAME => language.errors.name.to_string(),
|
||||||
Error::VALUE => language.errors.value.to_string(),
|
Error::VALUE => language.errors.value.to_string(),
|
||||||
Error::DIV => language.errors.div.to_string(),
|
Error::DIV => language.errors.div.to_string(),
|
||||||
@@ -135,7 +137,7 @@ impl Error {
|
|||||||
|
|
||||||
pub fn get_error_by_name(name: &str, language: &Language) -> Option<Error> {
|
pub fn get_error_by_name(name: &str, language: &Language) -> Option<Error> {
|
||||||
let errors = &language.errors;
|
let errors = &language.errors;
|
||||||
if name == errors.r#ref {
|
if name == errors.ref_value {
|
||||||
return Some(Error::REF);
|
return Some(Error::REF);
|
||||||
} else if name == errors.name {
|
} else if name == errors.name {
|
||||||
return Some(Error::NAME);
|
return Some(Error::NAME);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use chrono::DateTime;
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use chrono::Months;
|
use chrono::Months;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono::TimeZone;
|
||||||
use chrono::Timelike;
|
use chrono::Timelike;
|
||||||
|
|
||||||
use crate::expressions::types::CellReferenceIndex;
|
use crate::expressions::types::CellReferenceIndex;
|
||||||
@@ -257,8 +258,8 @@ impl Model {
|
|||||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||||
Some(dt) => dt.with_timezone(&self.tz),
|
Some(dt) => dt,
|
||||||
None => {
|
None => {
|
||||||
return CalcResult::Error {
|
return CalcResult::Error {
|
||||||
error: Error::ERROR,
|
error: Error::ERROR,
|
||||||
@@ -267,6 +268,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let local_time = self.tz.from_utc_datetime(&dt);
|
||||||
// 693_594 is computed as:
|
// 693_594 is computed as:
|
||||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||||
// The 2 days offset is because of Excel 1900 bug
|
// The 2 days offset is because of Excel 1900 bug
|
||||||
@@ -287,8 +289,8 @@ impl Model {
|
|||||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||||
Some(dt) => dt.with_timezone(&self.tz),
|
Some(dt) => dt,
|
||||||
None => {
|
None => {
|
||||||
return CalcResult::Error {
|
return CalcResult::Error {
|
||||||
error: Error::ERROR,
|
error: Error::ERROR,
|
||||||
@@ -297,6 +299,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let local_time = self.tz.from_utc_datetime(&dt);
|
||||||
// 693_594 is computed as:
|
// 693_594 is computed as:
|
||||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||||
// The 2 days offset is because of Excel 1900 bug
|
// The 2 days offset is because of Excel 1900 bug
|
||||||
|
|||||||
@@ -165,8 +165,7 @@ impl Model {
|
|||||||
message: "argument must be a reference to a single cell".to_string(),
|
message: "argument must be a reference to a single cell".to_string(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let is_formula = if let Ok(f) = self.get_cell_formula(left.sheet, left.row, left.column)
|
let is_formula = if let Ok(f) = self.cell_formula(left.sheet, left.row, left.column) {
|
||||||
{
|
|
||||||
f.is_some()
|
f.is_some()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::array::IntoIter;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_result::CalcResult,
|
calc_result::CalcResult,
|
||||||
@@ -245,206 +244,6 @@ pub enum Function {
|
|||||||
Subtotal,
|
Subtotal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
|
||||||
pub fn into_iter() -> IntoIter<Function, 192> {
|
|
||||||
[
|
|
||||||
Function::And,
|
|
||||||
Function::False,
|
|
||||||
Function::If,
|
|
||||||
Function::Iferror,
|
|
||||||
Function::Ifna,
|
|
||||||
Function::Ifs,
|
|
||||||
Function::Not,
|
|
||||||
Function::Or,
|
|
||||||
Function::Switch,
|
|
||||||
Function::True,
|
|
||||||
Function::Xor,
|
|
||||||
Function::Sin,
|
|
||||||
Function::Cos,
|
|
||||||
Function::Tan,
|
|
||||||
Function::Asin,
|
|
||||||
Function::Acos,
|
|
||||||
Function::Atan,
|
|
||||||
Function::Sinh,
|
|
||||||
Function::Cosh,
|
|
||||||
Function::Tanh,
|
|
||||||
Function::Asinh,
|
|
||||||
Function::Acosh,
|
|
||||||
Function::Atanh,
|
|
||||||
Function::Abs,
|
|
||||||
Function::Pi,
|
|
||||||
Function::Sqrt,
|
|
||||||
Function::Sqrtpi,
|
|
||||||
Function::Atan2,
|
|
||||||
Function::Power,
|
|
||||||
Function::Max,
|
|
||||||
Function::Min,
|
|
||||||
Function::Product,
|
|
||||||
Function::Rand,
|
|
||||||
Function::Randbetween,
|
|
||||||
Function::Round,
|
|
||||||
Function::Rounddown,
|
|
||||||
Function::Roundup,
|
|
||||||
Function::Sum,
|
|
||||||
Function::Sumif,
|
|
||||||
Function::Sumifs,
|
|
||||||
Function::Choose,
|
|
||||||
Function::Column,
|
|
||||||
Function::Columns,
|
|
||||||
Function::Index,
|
|
||||||
Function::Indirect,
|
|
||||||
Function::Hlookup,
|
|
||||||
Function::Lookup,
|
|
||||||
Function::Match,
|
|
||||||
Function::Offset,
|
|
||||||
Function::Row,
|
|
||||||
Function::Rows,
|
|
||||||
Function::Vlookup,
|
|
||||||
Function::Xlookup,
|
|
||||||
Function::Concatenate,
|
|
||||||
Function::Exact,
|
|
||||||
Function::Value,
|
|
||||||
Function::T,
|
|
||||||
Function::Valuetotext,
|
|
||||||
Function::Concat,
|
|
||||||
Function::Find,
|
|
||||||
Function::Left,
|
|
||||||
Function::Len,
|
|
||||||
Function::Lower,
|
|
||||||
Function::Mid,
|
|
||||||
Function::Right,
|
|
||||||
Function::Search,
|
|
||||||
Function::Text,
|
|
||||||
Function::Trim,
|
|
||||||
Function::Upper,
|
|
||||||
Function::Isnumber,
|
|
||||||
Function::Isnontext,
|
|
||||||
Function::Istext,
|
|
||||||
Function::Islogical,
|
|
||||||
Function::Isblank,
|
|
||||||
Function::Iserr,
|
|
||||||
Function::Iserror,
|
|
||||||
Function::Isna,
|
|
||||||
Function::Na,
|
|
||||||
Function::Isref,
|
|
||||||
Function::Isodd,
|
|
||||||
Function::Iseven,
|
|
||||||
Function::ErrorType,
|
|
||||||
Function::Isformula,
|
|
||||||
Function::Type,
|
|
||||||
Function::Sheet,
|
|
||||||
Function::Average,
|
|
||||||
Function::Averagea,
|
|
||||||
Function::Averageif,
|
|
||||||
Function::Averageifs,
|
|
||||||
Function::Count,
|
|
||||||
Function::Counta,
|
|
||||||
Function::Countblank,
|
|
||||||
Function::Countif,
|
|
||||||
Function::Countifs,
|
|
||||||
Function::Maxifs,
|
|
||||||
Function::Minifs,
|
|
||||||
Function::Year,
|
|
||||||
Function::Day,
|
|
||||||
Function::Month,
|
|
||||||
Function::Eomonth,
|
|
||||||
Function::Date,
|
|
||||||
Function::Edate,
|
|
||||||
Function::Today,
|
|
||||||
Function::Now,
|
|
||||||
Function::Pmt,
|
|
||||||
Function::Pv,
|
|
||||||
Function::Rate,
|
|
||||||
Function::Nper,
|
|
||||||
Function::Fv,
|
|
||||||
Function::Ppmt,
|
|
||||||
Function::Ipmt,
|
|
||||||
Function::Npv,
|
|
||||||
Function::Mirr,
|
|
||||||
Function::Irr,
|
|
||||||
Function::Xirr,
|
|
||||||
Function::Xnpv,
|
|
||||||
Function::Rept,
|
|
||||||
Function::Textafter,
|
|
||||||
Function::Textbefore,
|
|
||||||
Function::Textjoin,
|
|
||||||
Function::Substitute,
|
|
||||||
Function::Ispmt,
|
|
||||||
Function::Rri,
|
|
||||||
Function::Sln,
|
|
||||||
Function::Syd,
|
|
||||||
Function::Nominal,
|
|
||||||
Function::Effect,
|
|
||||||
Function::Pduration,
|
|
||||||
Function::Tbillyield,
|
|
||||||
Function::Tbillprice,
|
|
||||||
Function::Tbilleq,
|
|
||||||
Function::Dollarde,
|
|
||||||
Function::Dollarfr,
|
|
||||||
Function::Ddb,
|
|
||||||
Function::Db,
|
|
||||||
Function::Cumprinc,
|
|
||||||
Function::Cumipmt,
|
|
||||||
Function::Besseli,
|
|
||||||
Function::Besselj,
|
|
||||||
Function::Besselk,
|
|
||||||
Function::Bessely,
|
|
||||||
Function::Erf,
|
|
||||||
Function::ErfPrecise,
|
|
||||||
Function::Erfc,
|
|
||||||
Function::ErfcPrecise,
|
|
||||||
Function::Bin2dec,
|
|
||||||
Function::Bin2hex,
|
|
||||||
Function::Bin2oct,
|
|
||||||
Function::Dec2Bin,
|
|
||||||
Function::Dec2hex,
|
|
||||||
Function::Dec2oct,
|
|
||||||
Function::Hex2bin,
|
|
||||||
Function::Hex2dec,
|
|
||||||
Function::Hex2oct,
|
|
||||||
Function::Oct2bin,
|
|
||||||
Function::Oct2dec,
|
|
||||||
Function::Oct2hex,
|
|
||||||
Function::Bitand,
|
|
||||||
Function::Bitlshift,
|
|
||||||
Function::Bitor,
|
|
||||||
Function::Bitrshift,
|
|
||||||
Function::Bitxor,
|
|
||||||
Function::Complex,
|
|
||||||
Function::Imabs,
|
|
||||||
Function::Imaginary,
|
|
||||||
Function::Imargument,
|
|
||||||
Function::Imconjugate,
|
|
||||||
Function::Imcos,
|
|
||||||
Function::Imcosh,
|
|
||||||
Function::Imcot,
|
|
||||||
Function::Imcsc,
|
|
||||||
Function::Imcsch,
|
|
||||||
Function::Imdiv,
|
|
||||||
Function::Imexp,
|
|
||||||
Function::Imln,
|
|
||||||
Function::Imlog10,
|
|
||||||
Function::Imlog2,
|
|
||||||
Function::Impower,
|
|
||||||
Function::Improduct,
|
|
||||||
Function::Imreal,
|
|
||||||
Function::Imsec,
|
|
||||||
Function::Imsech,
|
|
||||||
Function::Imsin,
|
|
||||||
Function::Imsinh,
|
|
||||||
Function::Imsqrt,
|
|
||||||
Function::Imsub,
|
|
||||||
Function::Imsum,
|
|
||||||
Function::Imtan,
|
|
||||||
Function::Convert,
|
|
||||||
Function::Delta,
|
|
||||||
Function::Gestep,
|
|
||||||
Function::Subtotal,
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
|
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
|
||||||
pub fn to_xlsx_string(&self) -> String {
|
pub fn to_xlsx_string(&self) -> String {
|
||||||
@@ -909,23 +708,7 @@ impl fmt::Display for Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Documentation for one function
|
|
||||||
pub struct Documentation {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
/// Produces documentation for all implemented functions
|
|
||||||
pub fn documentation() -> Vec<Documentation> {
|
|
||||||
let mut doc = Vec::new();
|
|
||||||
for function in Function::into_iter() {
|
|
||||||
doc.push(Documentation {
|
|
||||||
name: function.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
doc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn evaluate_function(
|
pub(crate) fn evaluate_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
kind: &Function,
|
kind: &Function,
|
||||||
@@ -1145,66 +928,3 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{BufRead, BufReader},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::functions::Function;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn function_iterator() {
|
|
||||||
// This checks that the number of functions in the enum is the same
|
|
||||||
// as the number of functions in the Iterator.
|
|
||||||
|
|
||||||
// This is tricky. In Rust we cannot loop over all the members of an enum.
|
|
||||||
// There are alternatives like using an external crate like strum.
|
|
||||||
// But I am not in the mood for that.
|
|
||||||
|
|
||||||
// What we do here is read this file , extract the functions in the enum
|
|
||||||
// and check they are the same as in the iterator
|
|
||||||
let file = File::open("src/functions/mod.rs").unwrap();
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
let mut start = false;
|
|
||||||
let mut list = Vec::new();
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
let text = line.unwrap();
|
|
||||||
let text = text.trim().trim_end_matches(',');
|
|
||||||
if text == "pub enum Function {" {
|
|
||||||
start = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if start {
|
|
||||||
if text == "}" {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if text.starts_with("//") {
|
|
||||||
// skip comments
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if text.is_empty() {
|
|
||||||
// skip empty lines
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
list.push(text.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We make a list with their functions names, but we escape ".": ERROR.TYPE => ERRORTYPE
|
|
||||||
let iter_list = Function::into_iter()
|
|
||||||
.map(|f| format!("{}", f).replace('.', ""))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let len = iter_list.len();
|
|
||||||
|
|
||||||
assert_eq!(list.len(), len);
|
|
||||||
// We still need to check there are no duplicates. This will fail if a function in iter_list
|
|
||||||
// is included twice and one is missing
|
|
||||||
for function in list {
|
|
||||||
assert!(iter_list.contains(&function.to_uppercase()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
PfrendeesD<>VRAITRUEWAHRVERDADEROTVFAUXFALSEFALSCHFALSOUw#REF!#REF!#BEZUG!#¡REF!e<>#NOM?#NAME?#NAME?#¿NOMBRE?x<>#VALEUR!#VALUE!#WERT!#¡VALOR!w<>#DIV/0!#DIV/0!#DIV/0!#¡DIV/0!<04>#N/A#N/A#NV#N/AXv#NOMBRE!#NUM!#ZAHL!#¡NUM!<02><>#N/IMPL!#N/IMPL!#N/IMPL!#N/IMPL!w{#SPILL!#SPILL!#ÜBERLAUF!#SPILL!ff#CALC!#CALC!#CALC!#CALC!ff#CIRC!#CIRC!#CIRC!#CIRC!ww#ERROR!#ERROR!#ERROR!#ERROR!ff#NULL!#NULL!#NULL!#NULL!
|
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
|
||||||
pub struct Booleans {
|
pub struct Booleans {
|
||||||
pub r#true: String,
|
#[serde(rename = "true")]
|
||||||
pub r#false: String,
|
pub true_value: String,
|
||||||
|
#[serde(rename = "false")]
|
||||||
|
pub false_value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Errors {
|
pub struct Errors {
|
||||||
pub r#ref: String,
|
#[serde(rename = "ref")]
|
||||||
|
pub ref_value: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub div: String,
|
pub div: String,
|
||||||
@@ -25,14 +28,14 @@ pub struct Errors {
|
|||||||
pub null: String,
|
pub null: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
pub booleans: Booleans,
|
pub booleans: Booleans,
|
||||||
pub errors: Errors,
|
pub errors: Errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
||||||
bitcode::decode(include_bytes!("language.bin")).expect("Failed parsing language file")
|
serde_json::from_str(include_str!("language.json")).expect("Failed parsing language file")
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn get_language(id: &str) -> Result<&Language, String> {
|
pub fn get_language(id: &str) -> Result<&Language, String> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies]
|
//! [dependencies]
|
||||||
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc" }
|
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
|
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
|
||||||
@@ -31,21 +31,23 @@ pub mod expressions;
|
|||||||
pub mod formatter;
|
pub mod formatter;
|
||||||
pub mod language;
|
pub mod language;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
|
pub mod model;
|
||||||
pub mod new_empty;
|
pub mod new_empty;
|
||||||
pub mod number_format;
|
pub mod number_format;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod worksheet;
|
pub mod worksheet;
|
||||||
|
|
||||||
|
mod functions;
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod cast;
|
mod cast;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod diffs;
|
|
||||||
mod functions;
|
|
||||||
mod implicit_intersection;
|
|
||||||
mod model;
|
|
||||||
mod styles;
|
mod styles;
|
||||||
|
|
||||||
|
mod diffs;
|
||||||
|
mod implicit_intersection;
|
||||||
|
|
||||||
mod units;
|
mod units;
|
||||||
mod user_model;
|
|
||||||
mod utils;
|
mod utils;
|
||||||
mod workbook;
|
mod workbook;
|
||||||
|
|
||||||
@@ -54,9 +56,3 @@ mod test;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod mock_time;
|
pub mod mock_time;
|
||||||
|
|
||||||
pub use model::get_milliseconds_since_epoch;
|
|
||||||
pub use model::Model;
|
|
||||||
pub use user_model::BorderArea;
|
|
||||||
pub use user_model::ClipboardData;
|
|
||||||
pub use user_model::UserModel;
|
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
use bitcode::{Decode, Encode};
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Locale {
|
pub struct Locale {
|
||||||
pub dates: Dates,
|
pub dates: Dates,
|
||||||
pub numbers: NumbersProperties,
|
pub numbers: NumbersProperties,
|
||||||
pub currency: Currency,
|
pub currency: Currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Currency {
|
pub struct Currency {
|
||||||
pub iso: String,
|
pub iso: String,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NumbersProperties {
|
pub struct NumbersProperties {
|
||||||
|
#[serde(rename = "symbols-numberSystem-latn")]
|
||||||
pub symbols: NumbersSymbols,
|
pub symbols: NumbersSymbols,
|
||||||
|
#[serde(rename = "decimalFormats-numberSystem-latn")]
|
||||||
pub decimal_formats: DecimalFormats,
|
pub decimal_formats: DecimalFormats,
|
||||||
|
#[serde(rename = "currencyFormats-numberSystem-latn")]
|
||||||
pub currency_formats: CurrencyFormats,
|
pub currency_formats: CurrencyFormats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Dates {
|
pub struct Dates {
|
||||||
pub day_names: Vec<String>,
|
pub day_names: Vec<String>,
|
||||||
pub day_names_short: Vec<String>,
|
pub day_names_short: Vec<String>,
|
||||||
@@ -32,7 +35,8 @@ pub struct Dates {
|
|||||||
pub months_letter: Vec<String>,
|
pub months_letter: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NumbersSymbols {
|
pub struct NumbersSymbols {
|
||||||
pub decimal: String,
|
pub decimal: String,
|
||||||
pub group: String,
|
pub group: String,
|
||||||
@@ -50,26 +54,40 @@ pub struct NumbersSymbols {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct CurrencyFormats {
|
pub struct CurrencyFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
|
#[serde(rename = "standard-alphaNextToNumber")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub standard_alpha_next_to_number: Option<String>,
|
pub standard_alpha_next_to_number: Option<String>,
|
||||||
|
#[serde(rename = "standard-noCurrency")]
|
||||||
pub standard_no_currency: String,
|
pub standard_no_currency: String,
|
||||||
pub accounting: String,
|
pub accounting: String,
|
||||||
|
#[serde(rename = "accounting-alphaNextToNumber")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub accounting_alpha_next_to_number: Option<String>,
|
pub accounting_alpha_next_to_number: Option<String>,
|
||||||
|
#[serde(rename = "accounting-noCurrency")]
|
||||||
pub accounting_no_currency: String,
|
pub accounting_no_currency: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DecimalFormats {
|
pub struct DecimalFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LOCALES: Lazy<HashMap<String, Locale>> =
|
static LOCALES: Lazy<HashMap<String, Locale>> = Lazy::new(|| {
|
||||||
Lazy::new(|| bitcode::decode(include_bytes!("locales.bin")).expect("Failed parsing locale"));
|
serde_json::from_str(include_str!("locales.json")).expect("Failed parsing locale")
|
||||||
|
});
|
||||||
|
|
||||||
pub fn get_locale(id: &str) -> Result<&Locale, String> {
|
pub fn get_locale(_id: &str) -> Result<&Locale, String> {
|
||||||
// TODO: pass the locale once we implement locales in Rust
|
// TODO: pass the locale once we implement locales in Rust
|
||||||
|
let locale = LOCALES.get("en").ok_or("Invalid locale")?;
|
||||||
|
Ok(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this function one we implement locales properly
|
||||||
|
pub fn get_locale_fix(id: &str) -> Result<&Locale, String> {
|
||||||
let locale = LOCALES.get(id).ok_or("Invalid locale")?;
|
let locale = LOCALES.get(id).ok_or("Invalid locale")?;
|
||||||
Ok(locale)
|
Ok(locale)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::cell::RefCell;
|
|||||||
// 8 November 2022 12:13 Berlin time
|
// 8 November 2022 12:13 Berlin time
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static MOCK_TIME: RefCell<i64> = const { RefCell::new(1667906008578) };
|
static MOCK_TIME: RefCell<i64> = RefCell::new(1667906008578);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -20,18 +20,3 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
pub fn set_mock_time(time: i64) {
|
pub fn set_mock_time(time: i64) {
|
||||||
MOCK_TIME.with(|cell| *cell.borrow_mut() = time);
|
MOCK_TIME.with(|cell| *cell.borrow_mut() = time);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::mock_time::MOCK_TIME;
|
|
||||||
|
|
||||||
use super::get_milliseconds_since_epoch;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mock_time() {
|
|
||||||
let t = get_milliseconds_since_epoch();
|
|
||||||
assert_eq!(t, 1667906008578);
|
|
||||||
|
|
||||||
MOCK_TIME.with_borrow(|v| assert_eq!(*v, 1667906008578));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! # Model
|
||||||
|
//!
|
||||||
|
//! Note that sheets are 0-indexed and rows and columns are 1-indexed.
|
||||||
|
//!
|
||||||
|
//! IronCalc is row first. A cell is referenced by (`sheet`, `row`, `column`)
|
||||||
|
//!
|
||||||
|
|
||||||
|
use bincode::config;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
@@ -7,15 +17,19 @@ use crate::{
|
|||||||
calc_result::{CalcResult, Range},
|
calc_result::{CalcResult, Range},
|
||||||
cell::CellValue,
|
cell::CellValue,
|
||||||
constants::{self, LAST_COLUMN, LAST_ROW},
|
constants::{self, LAST_COLUMN, LAST_ROW},
|
||||||
|
expressions::token::{Error, OpCompare, OpProduct, OpSum, OpUnary},
|
||||||
|
expressions::{
|
||||||
|
parser::move_formula::{move_formula, MoveContext},
|
||||||
|
token::get_error_by_name,
|
||||||
|
types::*,
|
||||||
|
utils::{self, is_valid_row},
|
||||||
|
},
|
||||||
expressions::{
|
expressions::{
|
||||||
parser::{
|
parser::{
|
||||||
move_formula::{move_formula, MoveContext},
|
|
||||||
stringify::{to_rc_format, to_string},
|
stringify::{to_rc_format, to_string},
|
||||||
Node, Parser,
|
Node, Parser,
|
||||||
},
|
},
|
||||||
token::{get_error_by_name, Error, OpCompare, OpProduct, OpSum, OpUnary},
|
utils::is_valid_column_number,
|
||||||
types::*,
|
|
||||||
utils::{self, is_valid_column_number, is_valid_row},
|
|
||||||
},
|
},
|
||||||
formatter::{
|
formatter::{
|
||||||
format::{format_number, parse_formatted_number},
|
format::{format_number, parse_formatted_number},
|
||||||
@@ -34,11 +48,7 @@ use chrono_tz::Tz;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use crate::mock_time::get_milliseconds_since_epoch;
|
pub use crate::mock_time::get_milliseconds_since_epoch;
|
||||||
|
|
||||||
/// Number of milliseconds since January 1, 1970
|
/// wasm implementation for time
|
||||||
/// Used by time and date functions. It takes the value from the environment:
|
|
||||||
/// * The Operative System
|
|
||||||
/// * The JavaScript environment
|
|
||||||
/// * Or mocked for tests
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -49,11 +59,6 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
.as_millis() as i64
|
.as_millis() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of milliseconds since January 1, 1970
|
|
||||||
/// Used by time and date functions. It takes the value from the environment:
|
|
||||||
/// * The Operative System
|
|
||||||
/// * The JavaScript environment
|
|
||||||
/// * Or mocked for tests
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -63,7 +68,7 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
|
|
||||||
/// A cell might be evaluated or being evaluated
|
/// A cell might be evaluated or being evaluated
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum CellState {
|
pub enum CellState {
|
||||||
/// The cell has already been evaluated
|
/// The cell has already been evaluated
|
||||||
Evaluated,
|
Evaluated,
|
||||||
/// The cell is being evaluated
|
/// The cell is being evaluated
|
||||||
@@ -71,7 +76,7 @@ pub(crate) enum CellState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A parsed formula for a defined name
|
/// A parsed formula for a defined name
|
||||||
pub(crate) enum ParsedDefinedName {
|
pub enum ParsedDefinedName {
|
||||||
/// CellReference (`=C4`)
|
/// CellReference (`=C4`)
|
||||||
CellReference(CellReferenceIndex),
|
CellReference(CellReferenceIndex),
|
||||||
/// A Range (`=C4:D6`)
|
/// A Range (`=C4:D6`)
|
||||||
@@ -101,21 +106,19 @@ pub struct Model {
|
|||||||
/// A list of parsed formulas
|
/// A list of parsed formulas
|
||||||
pub parsed_formulas: Vec<Vec<Node>>,
|
pub parsed_formulas: Vec<Vec<Node>>,
|
||||||
/// A list of parsed defined names
|
/// A list of parsed defined names
|
||||||
pub(crate) parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
||||||
/// An optimization to lookup strings faster
|
/// An optimization to lookup strings faster
|
||||||
pub(crate) shared_strings: HashMap<String, usize>,
|
pub shared_strings: HashMap<String, usize>,
|
||||||
/// An instance of the parser
|
/// An instance of the parser
|
||||||
pub(crate) parser: Parser,
|
pub parser: Parser,
|
||||||
/// The list of cells with formulas that are evaluated of being evaluated
|
/// The list of cells with formulas that are evaluated of being evaluated
|
||||||
pub(crate) cells: HashMap<(u32, i32, i32), CellState>,
|
pub cells: HashMap<(u32, i32, i32), CellState>,
|
||||||
/// The locale of the model
|
/// The locale of the model
|
||||||
pub(crate) locale: Locale,
|
pub locale: Locale,
|
||||||
/// Tha language used
|
/// Tha language used
|
||||||
pub(crate) language: Language,
|
pub language: Language,
|
||||||
/// The timezone used to evaluate the model
|
/// The timezone used to evaluate the model
|
||||||
pub(crate) tz: Tz,
|
pub tz: Tz,
|
||||||
/// The view id. A view consist of a selected sheet and ranges.
|
|
||||||
pub(crate) view_id: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Maybe this should be the same as CellReference
|
// FIXME: Maybe this should be the same as CellReference
|
||||||
@@ -657,7 +660,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
|
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
|
||||||
@@ -679,13 +682,6 @@ impl Model {
|
|||||||
Err(format!("Invalid color: {}", color))
|
Err(format!("Invalid color: {}", color))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the grid lines in the sheet visible (`true`) or hidden (`false`)
|
|
||||||
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
|
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
|
||||||
worksheet.show_grid_lines = show_grid_lines;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_value(&self, cell: &Cell, cell_reference: CellReferenceIndex) -> CalcResult {
|
fn get_cell_value(&self, cell: &Cell, cell_reference: CellReferenceIndex) -> CalcResult {
|
||||||
use Cell::*;
|
use Cell::*;
|
||||||
match cell {
|
match cell {
|
||||||
@@ -731,7 +727,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
|
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
|
||||||
@@ -803,17 +799,17 @@ impl Model {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a model from an internal binary representation of a workbook
|
/// Returns a model from a String representation of a workbook
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
||||||
/// let model2 = Model::from_bytes(&model.to_bytes())?;
|
/// let model2 = Model::from_json(&model.to_json_str())?;
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// model2.get_cell_value_by_index(0, 1, 1),
|
/// model2.get_cell_value_by_index(0, 1, 1),
|
||||||
/// Ok(CellValue::String("Stella!".to_string()))
|
/// Ok(CellValue::String("Stella!".to_string()))
|
||||||
@@ -821,12 +817,9 @@ impl Model {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub fn from_json(s: &str) -> Result<Model, String> {
|
||||||
/// See also:
|
|
||||||
/// * [Model::to_bytes]
|
|
||||||
pub fn from_bytes(s: &[u8]) -> Result<Model, String> {
|
|
||||||
let workbook: Workbook =
|
let workbook: Workbook =
|
||||||
bitcode::decode(s).map_err(|e| format!("Error parsing workbook: {e}"))?;
|
serde_json::from_str(s).map_err(|_| "Error parsing workbook".to_string())?;
|
||||||
Model::from_workbook(workbook)
|
Model::from_workbook(workbook)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +828,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -891,7 +884,6 @@ impl Model {
|
|||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
tz,
|
tz,
|
||||||
view_id: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
@@ -905,7 +897,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -974,7 +966,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
|
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1045,7 +1037,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1092,7 +1084,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1147,13 +1139,13 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
|
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// let result = model.get_cell_formula(sheet, row, column)?;
|
/// let result = model.cell_formula(sheet, row, column)?;
|
||||||
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
|
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -1161,33 +1153,24 @@ impl Model {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [Model::get_cell_content()]
|
/// * [Model::get_cell_content()]
|
||||||
pub fn get_cell_formula(
|
pub fn cell_formula(
|
||||||
&self,
|
&self,
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
row: i32,
|
row: i32,
|
||||||
column: i32,
|
column: i32,
|
||||||
) -> Result<Option<String>, String> {
|
) -> Result<Option<String>, String> {
|
||||||
let worksheet = self.workbook.worksheet(sheet)?;
|
let worksheet = self.workbook.worksheet(sheet)?;
|
||||||
match worksheet.cell(row, column) {
|
Ok(worksheet.cell(row, column).and_then(|cell| {
|
||||||
Some(cell) => match cell.get_formula() {
|
cell.get_formula().map(|formula_index| {
|
||||||
Some(formula_index) => {
|
let formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
|
||||||
let formula = &self
|
let cell_ref = CellReferenceRC {
|
||||||
.parsed_formulas
|
sheet: worksheet.get_name(),
|
||||||
.get(sheet as usize)
|
row,
|
||||||
.ok_or("missing sheet")?
|
column,
|
||||||
.get(formula_index as usize)
|
};
|
||||||
.ok_or("missing formula")?;
|
format!("={}", to_string(formula, &cell_ref))
|
||||||
let cell_ref = CellReferenceRC {
|
})
|
||||||
sheet: worksheet.get_name(),
|
}))
|
||||||
row,
|
|
||||||
column,
|
|
||||||
};
|
|
||||||
Ok(Some(format!("={}", to_string(formula, &cell_ref))))
|
|
||||||
}
|
|
||||||
None => Ok(None),
|
|
||||||
},
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of a cell with some text
|
/// Updates the value of a cell with some text
|
||||||
@@ -1196,14 +1179,14 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "Hello!".to_string())?;
|
/// model.set_user_input(sheet, row, column, "Hello!".to_string());
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "Hello!".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "Hello!".to_string());
|
||||||
///
|
///
|
||||||
/// model.update_cell_with_text(sheet, row, column, "Goodbye!")?;
|
/// model.update_cell_with_text(sheet, row, column, "Goodbye!");
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "Goodbye!".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "Goodbye!".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -1214,30 +1197,23 @@ impl Model {
|
|||||||
/// * [Model::update_cell_with_number()]
|
/// * [Model::update_cell_with_number()]
|
||||||
/// * [Model::update_cell_with_bool()]
|
/// * [Model::update_cell_with_bool()]
|
||||||
/// * [Model::update_cell_with_formula()]
|
/// * [Model::update_cell_with_formula()]
|
||||||
pub fn update_cell_with_text(
|
pub fn update_cell_with_text(&mut self, sheet: u32, row: i32, column: i32, value: &str) {
|
||||||
&mut self,
|
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: &str,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
|
||||||
let new_style_index;
|
let new_style_index;
|
||||||
if common::value_needs_quoting(value, &self.language) {
|
if common::value_needs_quoting(value, &self.language) {
|
||||||
new_style_index = self
|
new_style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_with_quote_prefix(style_index)?;
|
.get_style_with_quote_prefix(style_index);
|
||||||
} else if self.workbook.styles.style_is_quote_prefix(style_index) {
|
} else if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||||
new_style_index = self
|
new_style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_without_quote_prefix(style_index)?;
|
.get_style_without_quote_prefix(style_index);
|
||||||
} else {
|
} else {
|
||||||
new_style_index = style_index;
|
new_style_index = style_index;
|
||||||
}
|
}
|
||||||
|
self.set_cell_with_string(sheet, row, column, value, new_style_index);
|
||||||
self.set_cell_with_string(sheet, row, column, value, new_style_index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of a cell with a boolean value
|
/// Updates the value of a cell with a boolean value
|
||||||
@@ -1246,14 +1222,14 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "TRUE".to_string())?;
|
/// model.set_user_input(sheet, row, column, "TRUE".to_string());
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "TRUE".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "TRUE".to_string());
|
||||||
///
|
///
|
||||||
/// model.update_cell_with_bool(sheet, row, column, false)?;
|
/// model.update_cell_with_bool(sheet, row, column, false);
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "FALSE".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "FALSE".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -1264,22 +1240,17 @@ impl Model {
|
|||||||
/// * [Model::update_cell_with_number()]
|
/// * [Model::update_cell_with_number()]
|
||||||
/// * [Model::update_cell_with_text()]
|
/// * [Model::update_cell_with_text()]
|
||||||
/// * [Model::update_cell_with_formula()]
|
/// * [Model::update_cell_with_formula()]
|
||||||
pub fn update_cell_with_bool(
|
pub fn update_cell_with_bool(&mut self, sheet: u32, row: i32, column: i32, value: bool) {
|
||||||
&mut self,
|
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: bool,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
|
||||||
let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
|
let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||||
self.workbook
|
self.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_without_quote_prefix(style_index)?
|
.get_style_without_quote_prefix(style_index)
|
||||||
} else {
|
} else {
|
||||||
style_index
|
style_index
|
||||||
};
|
};
|
||||||
self.set_cell_with_boolean(sheet, row, column, value, new_style_index)
|
let worksheet = &mut self.workbook.worksheets[sheet as usize];
|
||||||
|
worksheet.set_cell_with_boolean(row, column, value, new_style_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of a cell with a number
|
/// Updates the value of a cell with a number
|
||||||
@@ -1288,14 +1259,14 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "42".to_string())?;
|
/// model.set_user_input(sheet, row, column, "42".to_string());
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "42".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "42".to_string());
|
||||||
///
|
///
|
||||||
/// model.update_cell_with_number(sheet, row, column, 23.0)?;
|
/// model.update_cell_with_number(sheet, row, column, 23.0);
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "23".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "23".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -1306,22 +1277,17 @@ impl Model {
|
|||||||
/// * [Model::update_cell_with_text()]
|
/// * [Model::update_cell_with_text()]
|
||||||
/// * [Model::update_cell_with_bool()]
|
/// * [Model::update_cell_with_bool()]
|
||||||
/// * [Model::update_cell_with_formula()]
|
/// * [Model::update_cell_with_formula()]
|
||||||
pub fn update_cell_with_number(
|
pub fn update_cell_with_number(&mut self, sheet: u32, row: i32, column: i32, value: f64) {
|
||||||
&mut self,
|
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: f64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
|
||||||
let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
|
let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||||
self.workbook
|
self.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_without_quote_prefix(style_index)?
|
.get_style_without_quote_prefix(style_index)
|
||||||
} else {
|
} else {
|
||||||
style_index
|
style_index
|
||||||
};
|
};
|
||||||
self.set_cell_with_number(sheet, row, column, value, new_style_index)
|
let worksheet = &mut self.workbook.worksheets[sheet as usize];
|
||||||
|
worksheet.set_cell_with_number(row, column, value, new_style_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the formula of given cell
|
/// Updates the formula of given cell
|
||||||
@@ -1331,15 +1297,15 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "=A2*2".to_string())?;
|
/// model.set_user_input(sheet, row, column, "=A2*2".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "=A2*2".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "=A2*2".to_string());
|
||||||
///
|
///
|
||||||
/// model.update_cell_with_formula(sheet, row, column, "=A3*2".to_string())?;
|
/// model.update_cell_with_formula(sheet, row, column, "=A3*2".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "=A3*2".to_string());
|
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "=A3*2".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@@ -1358,12 +1324,12 @@ impl Model {
|
|||||||
column: i32,
|
column: i32,
|
||||||
formula: String,
|
formula: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut style_index = self.get_cell_style_index(sheet, row, column)?;
|
let mut style_index = self.get_cell_style_index(sheet, row, column);
|
||||||
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||||
style_index = self
|
style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_without_quote_prefix(style_index)?;
|
.get_style_without_quote_prefix(style_index);
|
||||||
}
|
}
|
||||||
let formula = formula
|
let formula = formula
|
||||||
.strip_prefix('=')
|
.strip_prefix('=')
|
||||||
@@ -1383,7 +1349,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1393,7 +1359,7 @@ impl Model {
|
|||||||
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
|
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
|
||||||
/// assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
|
/// assert_eq!(model.formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -1403,36 +1369,31 @@ impl Model {
|
|||||||
/// * [Model::update_cell_with_number()]
|
/// * [Model::update_cell_with_number()]
|
||||||
/// * [Model::update_cell_with_bool()]
|
/// * [Model::update_cell_with_bool()]
|
||||||
/// * [Model::update_cell_with_text()]
|
/// * [Model::update_cell_with_text()]
|
||||||
pub fn set_user_input(
|
pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, value: String) {
|
||||||
&mut self,
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: String,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// If value starts with "'" then we force the style to be quote_prefix
|
// If value starts with "'" then we force the style to be quote_prefix
|
||||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||||
if let Some(new_value) = value.strip_prefix('\'') {
|
if let Some(new_value) = value.strip_prefix('\'') {
|
||||||
// First check if it needs quoting
|
// First check if it needs quoting
|
||||||
let new_style = if common::value_needs_quoting(new_value, &self.language) {
|
let new_style = if common::value_needs_quoting(new_value, &self.language) {
|
||||||
self.workbook
|
self.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_with_quote_prefix(style_index)?
|
.get_style_with_quote_prefix(style_index)
|
||||||
} else {
|
} else {
|
||||||
style_index
|
style_index
|
||||||
};
|
};
|
||||||
self.set_cell_with_string(sheet, row, column, new_value, new_style)?;
|
self.set_cell_with_string(sheet, row, column, new_value, new_style);
|
||||||
} else {
|
} else {
|
||||||
let mut new_style_index = style_index;
|
let mut new_style_index = style_index;
|
||||||
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||||
new_style_index = self
|
new_style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_without_quote_prefix(style_index)?;
|
.get_style_without_quote_prefix(style_index);
|
||||||
}
|
}
|
||||||
if let Some(formula) = value.strip_prefix('=') {
|
if let Some(formula) = value.strip_prefix('=') {
|
||||||
let formula_index =
|
let formula_index = self
|
||||||
self.set_cell_with_formula(sheet, row, column, formula, new_style_index)?;
|
.set_cell_with_formula(sheet, row, column, formula, new_style_index)
|
||||||
|
.expect("could not set the cell formula");
|
||||||
// Update the style if needed
|
// Update the style if needed
|
||||||
let cell = CellReferenceIndex { sheet, row, column };
|
let cell = CellReferenceIndex { sheet, row, column };
|
||||||
let parsed_formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
|
let parsed_formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
|
||||||
@@ -1440,11 +1401,15 @@ impl Model {
|
|||||||
let new_style_index = self
|
let new_style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_with_format(new_style_index, &units.get_num_fmt())?;
|
.get_style_with_format(new_style_index, &units.get_num_fmt());
|
||||||
let style = self.workbook.styles.get_style(new_style_index)?;
|
let style = self.workbook.styles.get_style(new_style_index);
|
||||||
self.set_cell_style(sheet, row, column, &style)?
|
self.set_cell_style(sheet, row, column, &style)
|
||||||
|
.expect("Failed setting the style");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let worksheets = &mut self.workbook.worksheets;
|
||||||
|
let worksheet = &mut worksheets[sheet as usize];
|
||||||
|
|
||||||
// The list of currencies is '$', '€' and the local currency
|
// The list of currencies is '$', '€' and the local currency
|
||||||
let mut currencies = vec!["$", "€"];
|
let mut currencies = vec!["$", "€"];
|
||||||
let currency = &self.locale.currency.symbol;
|
let currency = &self.locale.currency.symbol;
|
||||||
@@ -1457,39 +1422,35 @@ impl Model {
|
|||||||
// Should not apply the format in the following cases:
|
// Should not apply the format in the following cases:
|
||||||
// - we assign a date to already date-formatted cell
|
// - we assign a date to already date-formatted cell
|
||||||
let should_apply_format = !(is_likely_date_number_format(
|
let should_apply_format = !(is_likely_date_number_format(
|
||||||
&self.workbook.styles.get_style(new_style_index)?.num_fmt,
|
&self.workbook.styles.get_style(new_style_index).num_fmt,
|
||||||
) && is_likely_date_number_format(&num_fmt));
|
) && is_likely_date_number_format(&num_fmt));
|
||||||
if should_apply_format {
|
if should_apply_format {
|
||||||
new_style_index = self
|
new_style_index = self
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.get_style_with_format(new_style_index, &num_fmt)?;
|
.get_style_with_format(new_style_index, &num_fmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
worksheet.set_cell_with_number(row, column, v, new_style_index);
|
||||||
worksheet.set_cell_with_number(row, column, v, new_style_index)?;
|
return;
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
// We try to parse as boolean
|
// We try to parse as boolean
|
||||||
if let Ok(v) = value.to_lowercase().parse::<bool>() {
|
if let Ok(v) = value.to_lowercase().parse::<bool>() {
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
worksheet.set_cell_with_boolean(row, column, v, new_style_index);
|
||||||
worksheet.set_cell_with_boolean(row, column, v, new_style_index)?;
|
return;
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
// Check is it is error value
|
// Check is it is error value
|
||||||
let upper = value.to_uppercase();
|
let upper = value.to_uppercase();
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
|
||||||
match get_error_by_name(&upper, &self.language) {
|
match get_error_by_name(&upper, &self.language) {
|
||||||
Some(error) => {
|
Some(error) => {
|
||||||
worksheet.set_cell_with_error(row, column, error, new_style_index)?;
|
worksheet.set_cell_with_error(row, column, error, new_style_index);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.set_cell_with_string(sheet, row, column, &value, new_style_index)?;
|
self.set_cell_with_string(sheet, row, column, &value, new_style_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cell_with_formula(
|
fn set_cell_with_formula(
|
||||||
@@ -1530,66 +1491,24 @@ impl Model {
|
|||||||
self.parsed_formulas[sheet as usize].push(parsed_formula);
|
self.parsed_formulas[sheet as usize].push(parsed_formula);
|
||||||
formula_index = (shared_formulas.len() as i32) - 1;
|
formula_index = (shared_formulas.len() as i32) - 1;
|
||||||
}
|
}
|
||||||
worksheet.set_cell_with_formula(row, column, formula_index, style)?;
|
worksheet.set_cell_with_formula(row, column, formula_index, style);
|
||||||
Ok(formula_index)
|
Ok(formula_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cell_with_string(
|
fn set_cell_with_string(&mut self, sheet: u32, row: i32, column: i32, value: &str, style: i32) {
|
||||||
&mut self,
|
let worksheets = &mut self.workbook.worksheets;
|
||||||
sheet: u32,
|
let worksheet = &mut worksheets[sheet as usize];
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: &str,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
match self.shared_strings.get(value) {
|
match self.shared_strings.get(value) {
|
||||||
Some(string_index) => {
|
Some(string_index) => {
|
||||||
self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
|
worksheet.set_cell_with_string(row, column, *string_index as i32, style);
|
||||||
row,
|
|
||||||
column,
|
|
||||||
*string_index as i32,
|
|
||||||
style,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let string_index = self.workbook.shared_strings.len();
|
let string_index = self.workbook.shared_strings.len();
|
||||||
self.workbook.shared_strings.push(value.to_string());
|
self.workbook.shared_strings.push(value.to_string());
|
||||||
self.shared_strings.insert(value.to_string(), string_index);
|
self.shared_strings.insert(value.to_string(), string_index);
|
||||||
self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
|
worksheet.set_cell_with_string(row, column, string_index as i32, style);
|
||||||
row,
|
|
||||||
column,
|
|
||||||
string_index as i32,
|
|
||||||
style,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cell_with_boolean(
|
|
||||||
&mut self,
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: bool,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.workbook
|
|
||||||
.worksheet_mut(sheet)?
|
|
||||||
.set_cell_with_boolean(row, column, value, style)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_cell_with_number(
|
|
||||||
&mut self,
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: f64,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.workbook
|
|
||||||
.worksheet_mut(sheet)?
|
|
||||||
.set_cell_with_number(row, column, value, style)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Excel Value (Bool, Number, String) of a cell
|
/// Gets the Excel Value (Bool, Number, String) of a cell
|
||||||
@@ -1611,7 +1530,7 @@ impl Model {
|
|||||||
/// Returns the cell value for (`sheet`, `row`, `column`)
|
/// Returns the cell value for (`sheet`, `row`, `column`)
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [Model::get_formatted_cell_value()]
|
/// * [Model::formatted_cell_value()]
|
||||||
pub fn get_cell_value_by_index(
|
pub fn get_cell_value_by_index(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
@@ -1637,42 +1556,35 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::Model;
|
/// # use ironcalc_base::model::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
|
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
/// let result = model.formatted_cell_value(sheet, row, column)?;
|
||||||
/// assert_eq!(result, "0.333333333".to_string());
|
/// assert_eq!(result, "0.333333333".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_formatted_cell_value(
|
pub fn formatted_cell_value(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
row: i32,
|
row: i32,
|
||||||
column: i32,
|
column: i32,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
match self.workbook.worksheet(sheet_index)?.cell(row, column) {
|
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
|
||||||
Some(cell) => {
|
let cell = self
|
||||||
let format = self.get_style_for_cell(sheet_index, row, column)?.num_fmt;
|
.workbook
|
||||||
let formatted_value =
|
.worksheet(sheet_index)?
|
||||||
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
|
.cell(row, column)
|
||||||
format_number(value, &format, &self.locale).text
|
.cloned()
|
||||||
});
|
.unwrap_or_default();
|
||||||
Ok(formatted_value)
|
let formatted_value =
|
||||||
}
|
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
|
||||||
None => Ok("".to_string()),
|
format_number(value, &format, &self.locale).text
|
||||||
}
|
});
|
||||||
}
|
Ok(formatted_value)
|
||||||
|
|
||||||
/// Return the typeof a cell
|
|
||||||
pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<CellType, String> {
|
|
||||||
Ok(match self.workbook.worksheet(sheet)?.cell(row, column) {
|
|
||||||
Some(c) => c.get_type(),
|
|
||||||
None => CellType::Number,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string with the cell content. If there is a formula returns the formula
|
/// Returns a string with the cell content. If there is a formula returns the formula
|
||||||
@@ -1736,53 +1648,15 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the content of the cell but leaves the style.
|
/// Sets cell to empty. Can be used to delete value without affecting style.
|
||||||
///
|
pub fn set_cell_empty(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
||||||
/// See also:
|
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||||
/// * [Model::cell_clear_all()]
|
worksheet.set_cell_empty(row, column);
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use ironcalc_base::Model;
|
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
|
||||||
/// model.set_user_input(sheet, row, column, "100$".to_string());
|
|
||||||
/// model.cell_clear_contents(sheet, row, column);
|
|
||||||
/// model.set_user_input(sheet, row, column, "10".to_string());
|
|
||||||
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
|
||||||
/// assert_eq!(result, "10$".to_string());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn cell_clear_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
|
||||||
self.workbook
|
|
||||||
.worksheet_mut(sheet)?
|
|
||||||
.cell_clear_contents(row, column)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a cell by removing it from worksheet data. All content and style is removed.
|
/// Deletes a cell by removing it from worksheet data.
|
||||||
///
|
pub fn delete_cell(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
||||||
/// See also:
|
|
||||||
/// * [Model::cell_clear_contents()]
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use ironcalc_base::Model;
|
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
|
||||||
/// model.set_user_input(sheet, row, column, "100$".to_string());
|
|
||||||
/// model.cell_clear_all(sheet, row, column);
|
|
||||||
/// model.set_user_input(sheet, row, column, "10".to_string());
|
|
||||||
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
|
||||||
/// assert_eq!(result, "10".to_string());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
pub fn cell_clear_all(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||||
|
|
||||||
let sheet_data = &mut worksheet.sheet_data;
|
let sheet_data = &mut worksheet.sheet_data;
|
||||||
@@ -1794,66 +1668,65 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the style index for cell (`sheet`, `row`, `column`)
|
/// Returns the style index for cell (`sheet`, `row`, `column`)
|
||||||
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> Result<i32, String> {
|
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> i32 {
|
||||||
// First check the cell, then row, the column
|
// First check the cell, then row, the column
|
||||||
let cell = self.workbook.worksheet(sheet)?.cell(row, column);
|
let cell = self
|
||||||
|
.workbook
|
||||||
|
.worksheet(sheet)
|
||||||
|
.expect("Invalid sheet")
|
||||||
|
.cell(row, column);
|
||||||
match cell {
|
match cell {
|
||||||
Some(cell) => Ok(cell.get_style()),
|
Some(cell) => cell.get_style(),
|
||||||
None => {
|
None => {
|
||||||
let rows = &self.workbook.worksheet(sheet)?.rows;
|
let rows = &self.workbook.worksheets[sheet as usize].rows;
|
||||||
for r in rows {
|
for r in rows {
|
||||||
if r.r == row {
|
if r.r == row {
|
||||||
if r.custom_format {
|
if r.custom_format {
|
||||||
return Ok(r.s);
|
return r.s;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cols = &self.workbook.worksheet(sheet)?.cols;
|
let cols = &self.workbook.worksheets[sheet as usize].cols;
|
||||||
for c in cols.iter() {
|
for c in cols.iter() {
|
||||||
let min = c.min;
|
let min = c.min;
|
||||||
let max = c.max;
|
let max = c.max;
|
||||||
if column >= min && column <= max {
|
if column >= min && column <= max {
|
||||||
return Ok(c.style.unwrap_or(0));
|
return c.style.unwrap_or(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(0)
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the style for cell (`sheet`, `row`, `column`)
|
/// Returns the style for cell (`sheet`, `row`, `column`)
|
||||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
|
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Style {
|
||||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
|
||||||
let style = self.workbook.styles.get_style(style_index)?;
|
|
||||||
Ok(style)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an internal binary representation of the workbook
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
/// * [Model::from_bytes]
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
bitcode::encode(&self.workbook)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns data about the worksheets
|
|
||||||
pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
|
|
||||||
self.workbook
|
self.workbook
|
||||||
.worksheets
|
.styles
|
||||||
.iter()
|
.get_style(self.get_cell_style_index(sheet, row, column))
|
||||||
.map(|worksheet| SheetProperties {
|
}
|
||||||
name: worksheet.get_name(),
|
|
||||||
state: worksheet.state.to_string(),
|
/// Returns a JSON string of the workbook
|
||||||
color: worksheet.color.clone(),
|
pub fn to_json_str(&self) -> String {
|
||||||
sheet_id: worksheet.sheet_id,
|
match serde_json::to_string(&self.workbook) {
|
||||||
})
|
Ok(s) => s,
|
||||||
.collect()
|
Err(_) => {
|
||||||
|
// TODO, is this branch possible at all?
|
||||||
|
json!({"error": "Error stringifying workbook"}).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// bin
|
||||||
|
pub fn to_binary_str(&self) -> Vec<u8> {
|
||||||
|
let config = config::standard();
|
||||||
|
bincode::encode_to_vec(&self.workbook, config).expect("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns markup representation of the given `sheet`.
|
/// Returns markup representation of the given `sheet`.
|
||||||
pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
|
pub fn sheet_markup(&self, sheet: u32) -> Result<String, String> {
|
||||||
let worksheet = self.workbook.worksheet(sheet)?;
|
let worksheet = self.workbook.worksheet(sheet)?;
|
||||||
let dimension = worksheet.dimension();
|
let dimension = worksheet.dimension();
|
||||||
|
|
||||||
@@ -1863,11 +1736,11 @@ impl Model {
|
|||||||
let mut row_markup: Vec<String> = Vec::new();
|
let mut row_markup: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for column in 1..(dimension.max_column + 1) {
|
for column in 1..(dimension.max_column + 1) {
|
||||||
let mut cell_markup = match self.get_cell_formula(sheet, row, column)? {
|
let mut cell_markup = match self.cell_formula(sheet, row, column)? {
|
||||||
Some(formula) => formula,
|
Some(formula) => formula,
|
||||||
None => self.get_formatted_cell_value(sheet, row, column)?,
|
None => self.formatted_cell_value(sheet, row, column)?,
|
||||||
};
|
};
|
||||||
let style = self.get_style_for_cell(sheet, row, column)?;
|
let style = self.get_style_for_cell(sheet, row, column);
|
||||||
if style.font.b {
|
if style.font.b {
|
||||||
cell_markup = format!("**{cell_markup}**")
|
cell_markup = format!("**{cell_markup}**")
|
||||||
}
|
}
|
||||||
@@ -1904,7 +1777,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of frozen rows in `sheet`
|
/// Returns the number of frozen rows in `sheet`
|
||||||
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_rows)
|
Ok(worksheet.frozen_rows)
|
||||||
} else {
|
} else {
|
||||||
@@ -1913,7 +1786,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the number of frozen columns in `sheet`
|
/// Return the number of frozen columns in `sheet`
|
||||||
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_columns)
|
Ok(worksheet.frozen_columns)
|
||||||
} else {
|
} else {
|
||||||
@@ -1954,34 +1827,6 @@ impl Model {
|
|||||||
Err("Invalid sheet".to_string())
|
Err("Invalid sheet".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the width of a column
|
|
||||||
#[inline]
|
|
||||||
pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
|
|
||||||
self.workbook.worksheet(sheet)?.get_column_width(column)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the width of a column
|
|
||||||
#[inline]
|
|
||||||
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), String> {
|
|
||||||
self.workbook
|
|
||||||
.worksheet_mut(sheet)?
|
|
||||||
.set_column_width(column, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the height of a row
|
|
||||||
#[inline]
|
|
||||||
pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
|
|
||||||
self.workbook.worksheet(sheet)?.row_height(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the height of a row
|
|
||||||
#[inline]
|
|
||||||
pub fn set_row_height(&mut self, sheet: u32, column: i32, height: f64) -> Result<(), String> {
|
|
||||||
self.workbook
|
|
||||||
.worksheet_mut(sheet)?
|
|
||||||
.set_row_height(column, height)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
use chrono::DateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_result::Range,
|
calc_result::Range,
|
||||||
constants::{DEFAULT_WINDOW_HEIGH, DEFAULT_WINDOW_WIDTH},
|
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::LexerMode,
|
lexer::LexerMode,
|
||||||
parser::{
|
parser::stringify::{rename_sheet_in_node, to_rc_format},
|
||||||
stringify::{rename_sheet_in_node, to_rc_format},
|
parser::Parser,
|
||||||
Parser,
|
|
||||||
},
|
|
||||||
types::CellReferenceRC,
|
types::CellReferenceRC,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale,
|
locale::get_locale,
|
||||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||||
types::{
|
types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||||
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
|
||||||
},
|
|
||||||
utils::ParsedReference,
|
utils::ParsedReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,20 +33,7 @@ fn is_valid_sheet_name(name: &str) -> bool {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
||||||
fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&u32]) -> Worksheet {
|
fn new_empty_worksheet(name: &str, sheet_id: u32) -> Worksheet {
|
||||||
let mut views = HashMap::new();
|
|
||||||
for id in view_ids {
|
|
||||||
views.insert(
|
|
||||||
**id,
|
|
||||||
WorksheetView {
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Worksheet {
|
Worksheet {
|
||||||
cols: vec![],
|
cols: vec![],
|
||||||
rows: vec![],
|
rows: vec![],
|
||||||
@@ -66,8 +48,6 @@ impl Model {
|
|||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
frozen_columns: 0,
|
frozen_columns: 0,
|
||||||
frozen_rows: 0,
|
frozen_rows: 0,
|
||||||
show_grid_lines: true,
|
|
||||||
views,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,8 +122,8 @@ impl Model {
|
|||||||
self.parsed_defined_names = parsed_defined_names;
|
self.parsed_defined_names = parsed_defined_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reparses all formulas and defined names
|
// Reparses all formulas and defined names
|
||||||
pub(crate) fn reset_parsed_structures(&mut self) {
|
fn reset_parsed_structures(&mut self) {
|
||||||
self.parser
|
self.parser
|
||||||
.set_worksheets(self.workbook.get_worksheet_names());
|
.set_worksheets(self.workbook.get_worksheet_names());
|
||||||
self.parsed_formulas = vec![];
|
self.parsed_formulas = vec![];
|
||||||
@@ -154,10 +134,10 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a sheet with a automatically generated name
|
/// Adds a sheet with a automatically generated name
|
||||||
pub fn new_sheet(&mut self) -> (String, u32) {
|
pub fn new_sheet(&mut self) {
|
||||||
// First we find a name
|
// First we find a name
|
||||||
|
|
||||||
// TODO: The name should depend on the locale
|
// TODO: When/if we support i18n the name could depend on the locale
|
||||||
let base_name = "Sheet";
|
let base_name = "Sheet";
|
||||||
let base_name_uppercase = base_name.to_uppercase();
|
let base_name_uppercase = base_name.to_uppercase();
|
||||||
let mut index = 1;
|
let mut index = 1;
|
||||||
@@ -173,11 +153,9 @@ impl Model {
|
|||||||
let sheet_name = format!("{}{}", base_name, index);
|
let sheet_name = format!("{}{}", base_name, index);
|
||||||
// Now we need a sheet_id
|
// Now we need a sheet_id
|
||||||
let sheet_id = self.get_new_sheet_id();
|
let sheet_id = self.get_new_sheet_id();
|
||||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
||||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids);
|
|
||||||
self.workbook.worksheets.push(worksheet);
|
self.workbook.worksheets.push(worksheet);
|
||||||
self.reset_parsed_structures();
|
self.reset_parsed_structures();
|
||||||
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a sheet with a particular index
|
/// Inserts a sheet with a particular index
|
||||||
@@ -205,8 +183,7 @@ impl Model {
|
|||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => self.get_new_sheet_id(),
|
None => self.get_new_sheet_id(),
|
||||||
};
|
};
|
||||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
|
||||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids);
|
|
||||||
if sheet_index as usize > self.workbook.worksheets.len() {
|
if sheet_index as usize > self.workbook.worksheets.len() {
|
||||||
return Err("Sheet index out of range".to_string());
|
return Err("Sheet index out of range".to_string());
|
||||||
}
|
}
|
||||||
@@ -246,10 +223,10 @@ impl Model {
|
|||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !is_valid_sheet_name(new_name) {
|
if !is_valid_sheet_name(new_name) {
|
||||||
return Err(format!("Invalid name for a sheet: '{}'.", new_name));
|
return Err(format!("Invalid name for a sheet: '{}'", new_name));
|
||||||
}
|
}
|
||||||
if self.get_sheet_index_by_name(new_name).is_some() {
|
if self.get_sheet_index_by_name(new_name).is_some() {
|
||||||
return Err(format!("Sheet already exists: '{}'.", new_name));
|
return Err(format!("Sheet already exists: '{}'", new_name));
|
||||||
}
|
}
|
||||||
let worksheets = &self.workbook.worksheets;
|
let worksheets = &self.workbook.worksheets;
|
||||||
let sheet_count = worksheets.len() as u32;
|
let sheet_count = worksheets.len() as u32;
|
||||||
@@ -293,7 +270,7 @@ impl Model {
|
|||||||
if sheet_count == 1 {
|
if sheet_count == 1 {
|
||||||
return Err("Cannot delete only sheet".to_string());
|
return Err("Cannot delete only sheet".to_string());
|
||||||
};
|
};
|
||||||
if sheet_index >= sheet_count {
|
if sheet_index > sheet_count {
|
||||||
return Err("Sheet index too large".to_string());
|
return Err("Sheet index too large".to_string());
|
||||||
}
|
}
|
||||||
self.workbook.worksheets.remove(sheet_index as usize);
|
self.workbook.worksheets.remove(sheet_index as usize);
|
||||||
@@ -346,28 +323,18 @@ impl Model {
|
|||||||
|
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let dt = match DateTime::from_timestamp(seconds, 0) {
|
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
|
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
|
||||||
};
|
};
|
||||||
// "2020-08-06T21:20:53Z
|
// "2020-08-06T21:20:53Z
|
||||||
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
||||||
|
|
||||||
let mut views = HashMap::new();
|
|
||||||
views.insert(
|
|
||||||
0,
|
|
||||||
WorkbookView {
|
|
||||||
sheet: 0,
|
|
||||||
window_width: DEFAULT_WINDOW_WIDTH,
|
|
||||||
window_height: DEFAULT_WINDOW_HEIGH,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
||||||
let workbook = Workbook {
|
let workbook = Workbook {
|
||||||
shared_strings: vec![],
|
shared_strings: vec![],
|
||||||
defined_names: vec![],
|
defined_names: vec![],
|
||||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])],
|
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
|
||||||
styles: Default::default(),
|
styles: Default::default(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
settings: WorkbookSettings {
|
settings: WorkbookSettings {
|
||||||
@@ -383,7 +350,6 @@ impl Model {
|
|||||||
last_modified: now,
|
last_modified: now,
|
||||||
},
|
},
|
||||||
tables: HashMap::new(),
|
tables: HashMap::new(),
|
||||||
views,
|
|
||||||
};
|
};
|
||||||
let parsed_formulas = Vec::new();
|
let parsed_formulas = Vec::new();
|
||||||
let worksheets = &workbook.worksheets;
|
let worksheets = &workbook.worksheets;
|
||||||
@@ -404,7 +370,6 @@ impl Model {
|
|||||||
locale,
|
locale,
|
||||||
language,
|
language,
|
||||||
tz,
|
tz,
|
||||||
view_id: 0,
|
|
||||||
};
|
};
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
Ok(model)
|
Ok(model)
|
||||||
|
|||||||
@@ -161,29 +161,26 @@ impl Styles {
|
|||||||
|
|
||||||
pub fn create_named_style(&mut self, style_name: &str, style: &Style) -> Result<(), String> {
|
pub fn create_named_style(&mut self, style_name: &str, style: &Style) -> Result<(), String> {
|
||||||
let style_index = self.create_new_style(style);
|
let style_index = self.create_new_style(style);
|
||||||
self.add_named_cell_style(style_name, style_index)
|
self.add_named_cell_style(style_name, style_index)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
|
pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> i32 {
|
||||||
let mut style = self.get_style(index)?;
|
let mut style = self.get_style(index);
|
||||||
style.quote_prefix = true;
|
style.quote_prefix = true;
|
||||||
Ok(self.get_style_index_or_create(&style))
|
self.get_style_index_or_create(&style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_style_with_format(
|
pub(crate) fn get_style_with_format(&mut self, index: i32, num_fmt: &str) -> i32 {
|
||||||
&mut self,
|
let mut style = self.get_style(index);
|
||||||
index: i32,
|
|
||||||
num_fmt: &str,
|
|
||||||
) -> Result<i32, String> {
|
|
||||||
let mut style = self.get_style(index)?;
|
|
||||||
style.num_fmt = num_fmt.to_string();
|
style.num_fmt = num_fmt.to_string();
|
||||||
Ok(self.get_style_index_or_create(&style))
|
self.get_style_index_or_create(&style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
|
pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> i32 {
|
||||||
let mut style = self.get_style(index)?;
|
let mut style = self.get_style(index);
|
||||||
style.quote_prefix = false;
|
style.quote_prefix = false;
|
||||||
Ok(self.get_style_index_or_create(&style))
|
self.get_style_index_or_create(&style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn style_is_quote_prefix(&self, index: i32) -> bool {
|
pub(crate) fn style_is_quote_prefix(&self, index: i32) -> bool {
|
||||||
@@ -191,11 +188,9 @@ impl Styles {
|
|||||||
cell_xf.quote_prefix
|
cell_xf.quote_prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_style(&self, index: i32) -> Result<Style, String> {
|
pub(crate) fn get_style(&self, index: i32) -> Style {
|
||||||
let cell_xf = &self
|
let cell_xf = &self.cell_xfs[index as usize];
|
||||||
.cell_xfs
|
|
||||||
.get(index as usize)
|
|
||||||
.ok_or("Invalid index provided".to_string())?;
|
|
||||||
let border_id = cell_xf.border_id as usize;
|
let border_id = cell_xf.border_id as usize;
|
||||||
let fill_id = cell_xf.fill_id as usize;
|
let fill_id = cell_xf.fill_id as usize;
|
||||||
let font_id = cell_xf.font_id as usize;
|
let font_id = cell_xf.font_id as usize;
|
||||||
@@ -203,14 +198,14 @@ impl Styles {
|
|||||||
let quote_prefix = cell_xf.quote_prefix;
|
let quote_prefix = cell_xf.quote_prefix;
|
||||||
let alignment = cell_xf.alignment.clone();
|
let alignment = cell_xf.alignment.clone();
|
||||||
|
|
||||||
Ok(Style {
|
Style {
|
||||||
alignment,
|
alignment,
|
||||||
num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
|
num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
|
||||||
fill: self.fills[fill_id].clone(),
|
fill: self.fills[fill_id].clone(),
|
||||||
font: self.fonts[font_id].clone(),
|
font: self.fonts[font_id].clone(),
|
||||||
border: self.borders[border_id].clone(),
|
border: self.borders[border_id].clone(),
|
||||||
quote_prefix,
|
quote_prefix,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +221,8 @@ impl Model {
|
|||||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||||
self.workbook
|
self.workbook
|
||||||
.worksheet_mut(sheet)?
|
.worksheet_mut(sheet)?
|
||||||
.set_cell_style(row, column, style_index)
|
.set_cell_style(row, column, style_index);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_cell_style(
|
pub fn copy_cell_style(
|
||||||
@@ -241,7 +237,9 @@ impl Model {
|
|||||||
|
|
||||||
self.workbook
|
self.workbook
|
||||||
.worksheet_mut(destination_cell.0)?
|
.worksheet_mut(destination_cell.0)?
|
||||||
.set_cell_style(destination_cell.1, destination_cell.2, source_style_index)
|
.set_cell_style(destination_cell.1, destination_cell.2, source_style_index);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style "style_name" in cell
|
/// Sets the style "style_name" in cell
|
||||||
@@ -255,7 +253,8 @@ impl Model {
|
|||||||
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
|
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
|
||||||
self.workbook
|
self.workbook
|
||||||
.worksheet_mut(sheet)?
|
.worksheet_mut(sheet)?
|
||||||
.set_cell_style(row, column, style_index)
|
.set_cell_style(row, column, style_index);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sheet_style(&mut self, sheet: u32, style_name: &str) -> Result<(), String> {
|
pub fn set_sheet_style(&mut self, sheet: u32, style_name: &str) -> Result<(), String> {
|
||||||
|
|||||||
@@ -76,16 +76,10 @@ fn fn_imconjugate() {
|
|||||||
fn fn_imcos() {
|
fn fn_imcos() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", r#"=IMCOS("4+3i")"#);
|
model._set("A1", r#"=IMCOS("4+3i")"#);
|
||||||
// In macos non intel this is "-6.58066304055116+7.58155274274655i"
|
|
||||||
model._set("A2", r#"=COMPLEX(-6.58066304055116, 7.58155274274654)"#);
|
|
||||||
model._set("A3", r#"=IMABS(IMSUB(A1, A2)) < G1"#);
|
|
||||||
|
|
||||||
// small number
|
|
||||||
model._set("G1", "0.0000001");
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text("A3"), "TRUE");
|
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
mod test_actions;
|
mod test_actions;
|
||||||
mod test_binary_search;
|
mod test_binary_search;
|
||||||
mod test_cell;
|
mod test_cell;
|
||||||
mod test_cell_clear_contents;
|
|
||||||
mod test_circular_references;
|
mod test_circular_references;
|
||||||
mod test_column_width;
|
mod test_column_width;
|
||||||
mod test_criteria;
|
mod test_criteria;
|
||||||
@@ -29,8 +28,9 @@ mod test_frozen_rows_columns;
|
|||||||
mod test_general;
|
mod test_general;
|
||||||
mod test_math;
|
mod test_math;
|
||||||
mod test_metadata;
|
mod test_metadata;
|
||||||
mod test_model_cell_clear_all;
|
mod test_model_delete_cell;
|
||||||
mod test_model_is_empty_cell;
|
mod test_model_is_empty_cell;
|
||||||
|
mod test_model_set_cell_empty;
|
||||||
mod test_move_formula;
|
mod test_move_formula;
|
||||||
mod test_quote_prefix;
|
mod test_quote_prefix;
|
||||||
mod test_set_user_input;
|
mod test_set_user_input;
|
||||||
@@ -52,7 +52,4 @@ mod test_fn_type;
|
|||||||
mod test_frozen_rows_and_columns;
|
mod test_frozen_rows_and_columns;
|
||||||
mod test_get_cell_content;
|
mod test_get_cell_content;
|
||||||
mod test_percentage;
|
mod test_percentage;
|
||||||
mod test_set_functions_error_handling;
|
|
||||||
mod test_today;
|
mod test_today;
|
||||||
mod test_types;
|
|
||||||
mod user_model;
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use crate::constants::{DEFAULT_ROW_HEIGHT, LAST_COLUMN};
|
use crate::constants::LAST_COLUMN;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
use crate::types::Col;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_columns() {
|
fn test_insert_columns() {
|
||||||
@@ -87,8 +86,7 @@ fn test_insert_rows_styles() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||||
< f64::EPSILON
|
|
||||||
);
|
);
|
||||||
// sets height 42 in row 10
|
// sets height 42 in row 10
|
||||||
model
|
model
|
||||||
@@ -107,8 +105,7 @@ fn test_insert_rows_styles() {
|
|||||||
|
|
||||||
// Row 10 has the default height
|
// Row 10 has the default height
|
||||||
assert!(
|
assert!(
|
||||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||||
< f64::EPSILON
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Row 10 is now row 15
|
// Row 10 is now row 15
|
||||||
@@ -122,8 +119,7 @@ fn test_delete_rows_styles() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||||
< f64::EPSILON
|
|
||||||
);
|
);
|
||||||
// sets height 42 in row 10
|
// sets height 42 in row 10
|
||||||
model
|
model
|
||||||
@@ -142,8 +138,7 @@ fn test_delete_rows_styles() {
|
|||||||
|
|
||||||
// Row 10 has the default height
|
// Row 10 has the default height
|
||||||
assert!(
|
assert!(
|
||||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||||
< f64::EPSILON
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Row 10 is now row 5
|
// Row 10 is now row 5
|
||||||
@@ -200,250 +195,6 @@ fn test_delete_columns() {
|
|||||||
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
|
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_delete_column_width() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
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());
|
|
||||||
|
|
||||||
// delete it
|
|
||||||
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
|
||||||
|
|
||||||
// all the columns around have the expected width
|
|
||||||
assert_eq!(
|
|
||||||
model.get_column_width(sheet, column - 1).unwrap(),
|
|
||||||
normal_width
|
|
||||||
);
|
|
||||||
assert_eq!(model.get_column_width(sheet, column).unwrap(), normal_width);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_column_width(sheet, column + 1).unwrap(),
|
|
||||||
normal_width
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// We set the style of columns 4 to 7 and delete column 4
|
|
||||||
// We check that columns 4 to 6 have the new style
|
|
||||||
fn test_delete_first_column_width() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 4,
|
|
||||||
max: 7,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 4);
|
|
||||||
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 4,
|
|
||||||
max: 6,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// Delete the last column in the range
|
|
||||||
fn test_delete_last_column_width() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 4,
|
|
||||||
max: 7,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 7);
|
|
||||||
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 4,
|
|
||||||
max: 6,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// Deletes columns at the end
|
|
||||||
fn test_delete_last_few_columns_width() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 4,
|
|
||||||
max: 17,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 13);
|
|
||||||
assert!(model.delete_columns(sheet, column, 10).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 4,
|
|
||||||
max: 12,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_delete_columns_non_overlapping_left() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 10,
|
|
||||||
max: 17,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 3);
|
|
||||||
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 6,
|
|
||||||
max: 13,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_delete_columns_overlapping_left() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 10,
|
|
||||||
max: 20,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 8);
|
|
||||||
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 8,
|
|
||||||
max: 16,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_delete_columns_non_overlapping_right() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 10,
|
|
||||||
max: 17,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
let (sheet, column) = (0, 23);
|
|
||||||
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 10,
|
|
||||||
max: 17,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// deletes some columns in the middle of the range
|
|
||||||
fn test_delete_middle_column_width() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// styled columns [4, 17]
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 4,
|
|
||||||
max: 17,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
|
|
||||||
// deletes columns 10, 11, 12
|
|
||||||
let (sheet, column) = (0, 10);
|
|
||||||
assert!(model.delete_columns(sheet, column, 3).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
cols[0],
|
|
||||||
Col {
|
|
||||||
min: 4,
|
|
||||||
max: 14,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// the range is inside the deleted columns
|
|
||||||
fn delete_range_in_columns() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// styled columns [6, 10]
|
|
||||||
model.workbook.worksheets[0].cols = vec![Col {
|
|
||||||
min: 6,
|
|
||||||
max: 10,
|
|
||||||
width: 300.0,
|
|
||||||
custom_width: true,
|
|
||||||
style: None,
|
|
||||||
}];
|
|
||||||
|
|
||||||
// deletes columns [4, 17]
|
|
||||||
let (sheet, column) = (0, 4);
|
|
||||||
assert!(model.delete_columns(sheet, column, 8).is_ok());
|
|
||||||
let cols = &model.workbook.worksheets[0].cols;
|
|
||||||
assert_eq!(cols.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_delete_columns_error() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
let (sheet, column) = (0, 5);
|
|
||||||
assert!(model.delete_columns(sheet, column, -1).is_err());
|
|
||||||
assert!(model.delete_columns(sheet, column, 0).is_err());
|
|
||||||
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_rows() {
|
fn test_delete_rows() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ fn test_column_width() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!((worksheet.get_column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.get_column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.get_column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert_eq!(model.get_cell_style_index(0, 23, 2), Ok(6));
|
assert_eq!(model.get_cell_style_index(0, 23, 2), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -48,12 +48,10 @@ fn test_column_width_lower_edge() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!((worksheet.get_column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.get_column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!(
|
assert!((worksheet.column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON);
|
||||||
(worksheet.get_column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
|
assert_eq!(model.get_cell_style_index(0, 23, 5), 1);
|
||||||
);
|
|
||||||
assert_eq!(model.get_cell_style_index(0, 23, 5), Ok(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -76,9 +74,9 @@ fn test_column_width_higher_edge() {
|
|||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
(worksheet.get_column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
|
(worksheet.column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
|
||||||
);
|
);
|
||||||
assert!((worksheet.get_column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.get_column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert_eq!(model.get_cell_style_index(0, 23, 16), Ok(1));
|
assert_eq!(model.get_cell_style_index(0, 23, 16), 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,37 +8,34 @@ use crate::{
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_empty_model() {
|
fn test_empty_model() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
|
assert_eq!(model.get_frozen_rows(0), Ok(0));
|
||||||
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
||||||
|
|
||||||
let e = model.set_frozen_rows(0, 3);
|
let e = model.set_frozen_rows(0, 3);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
|
assert_eq!(model.get_frozen_rows(0), Ok(3));
|
||||||
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
||||||
|
|
||||||
let e = model.set_frozen_columns(0, 53);
|
let e = model.set_frozen_columns(0, 53);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
|
assert_eq!(model.get_frozen_rows(0), Ok(3));
|
||||||
assert_eq!(model.get_frozen_columns_count(0), Ok(53));
|
assert_eq!(model.get_frozen_columns(0), Ok(53));
|
||||||
|
|
||||||
// Set them back to zero
|
// Set them back to zero
|
||||||
let e = model.set_frozen_rows(0, 0);
|
let e = model.set_frozen_rows(0, 0);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
let e = model.set_frozen_columns(0, 0);
|
let e = model.set_frozen_columns(0, 0);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
|
assert_eq!(model.get_frozen_rows(0), Ok(0));
|
||||||
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_sheet() {
|
fn test_invalid_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
assert_eq!(model.get_frozen_rows(1), Err("Invalid sheet".to_string()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_frozen_rows_count(1),
|
model.get_frozen_columns(3),
|
||||||
Err("Invalid sheet".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_frozen_columns_count(3),
|
|
||||||
Err("Invalid sheet".to_string())
|
Err("Invalid sheet".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ fn test_empty_model() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_model_simple_evaluation() {
|
fn test_model_simple_evaluation() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "= 1 + 3".to_string());
|
||||||
.set_user_input(0, 1, 1, "= 1 + 3".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let result = model._get_text_at(0, 1, 1);
|
let result = model._get_text_at(0, 1, 1);
|
||||||
assert_eq!(result, *"4");
|
assert_eq!(result, *"4");
|
||||||
@@ -45,7 +43,7 @@ fn test_model_simple_evaluation_order() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_model_invalid_formula() {
|
fn test_model_invalid_formula() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "= 1 +".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "= 1 +".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let result = model._get_text_at(0, 1, 1);
|
let result = model._get_text_at(0, 1, 1);
|
||||||
assert_eq!(result, *"#ERROR!");
|
assert_eq!(result, *"#ERROR!");
|
||||||
@@ -56,10 +54,8 @@ fn test_model_invalid_formula() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_model_dependencies() {
|
fn test_model_dependencies() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "23".to_string()).unwrap(); // A1
|
model.set_user_input(0, 1, 1, "23".to_string()); // A1
|
||||||
model
|
model.set_user_input(0, 1, 2, "= A1* 2-4".to_string()); // B1
|
||||||
.set_user_input(0, 1, 2, "= A1* 2-4".to_string())
|
|
||||||
.unwrap(); // B1
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let result = model._get_text_at(0, 1, 1);
|
let result = model._get_text_at(0, 1, 1);
|
||||||
assert_eq!(result, *"23");
|
assert_eq!(result, *"23");
|
||||||
@@ -69,9 +65,7 @@ fn test_model_dependencies() {
|
|||||||
let result = model._get_formula("B1");
|
let result = model._get_formula("B1");
|
||||||
assert_eq!(result, *"=A1*2-4");
|
assert_eq!(result, *"=A1*2-4");
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 2, 1, "=SUM(A1, B1)".to_string()); // A2
|
||||||
.set_user_input(0, 2, 1, "=SUM(A1, B1)".to_string())
|
|
||||||
.unwrap(); // A2
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let result = model._get_text_at(0, 2, 1);
|
let result = model._get_text_at(0, 2, 1);
|
||||||
assert_eq!(result, *"65");
|
assert_eq!(result, *"65");
|
||||||
@@ -80,10 +74,8 @@ fn test_model_dependencies() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_model_strings() {
|
fn test_model_strings() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "Hello World".to_string());
|
||||||
.set_user_input(0, 1, 1, "Hello World".to_string())
|
model.set_user_input(0, 1, 2, "=A1".to_string());
|
||||||
.unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "=A1".to_string()).unwrap();
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let result = model._get_text_at(0, 1, 1);
|
let result = model._get_text_at(0, 1, 1);
|
||||||
assert_eq!(result, *"Hello World");
|
assert_eq!(result, *"Hello World");
|
||||||
@@ -160,35 +152,21 @@ fn test_to_excel_precision_str() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_booleans() {
|
fn test_booleans() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "true".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "true".to_string());
|
||||||
model.set_user_input(0, 2, 1, "TRUE".to_string()).unwrap();
|
model.set_user_input(0, 2, 1, "TRUE".to_string());
|
||||||
model.set_user_input(0, 3, 1, "True".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "True".to_string());
|
||||||
model.set_user_input(0, 4, 1, "false".to_string()).unwrap();
|
model.set_user_input(0, 4, 1, "false".to_string());
|
||||||
model.set_user_input(0, 5, 1, "FALSE".to_string()).unwrap();
|
model.set_user_input(0, 5, 1, "FALSE".to_string());
|
||||||
model.set_user_input(0, 6, 1, "False".to_string()).unwrap();
|
model.set_user_input(0, 6, 1, "False".to_string());
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string());
|
||||||
.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string())
|
model.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 3, 2, "=ISLOGICAL(A3)".to_string());
|
||||||
model
|
model.set_user_input(0, 4, 2, "=ISLOGICAL(A4)".to_string());
|
||||||
.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string())
|
model.set_user_input(0, 5, 2, "=ISLOGICAL(A5)".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 6, 2, "=ISLOGICAL(A6)".to_string());
|
||||||
model
|
|
||||||
.set_user_input(0, 3, 2, "=ISLOGICAL(A3)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 4, 2, "=ISLOGICAL(A4)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 5, 2, "=ISLOGICAL(A5)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 6, 2, "=ISLOGICAL(A6)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string());
|
||||||
.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -213,19 +191,19 @@ fn test_booleans() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_set_cell_style() {
|
fn test_set_cell_style() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(!style.font.b);
|
assert!(!style.font.b);
|
||||||
|
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(style.font.b);
|
assert!(style.font.b);
|
||||||
|
|
||||||
style.font.b = false;
|
style.font.b = false;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
|
|
||||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(!style.font.b);
|
assert!(!style.font.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,21 +211,21 @@ fn test_set_cell_style() {
|
|||||||
fn test_copy_cell_style() {
|
fn test_copy_cell_style() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 2).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 2);
|
||||||
style.font.i = true;
|
style.font.i = true;
|
||||||
assert!(model.set_cell_style(0, 1, 2, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 2, &style).is_ok());
|
||||||
|
|
||||||
assert!(model.copy_cell_style((0, 1, 1), (0, 1, 2)).is_ok());
|
assert!(model.copy_cell_style((0, 1, 1), (0, 1, 2)).is_ok());
|
||||||
|
|
||||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(style.font.b);
|
assert!(style.font.b);
|
||||||
assert!(!style.font.i);
|
assert!(!style.font.i);
|
||||||
|
|
||||||
let style = model.get_style_for_cell(0, 1, 2).unwrap();
|
let style = model.get_style_for_cell(0, 1, 2);
|
||||||
assert!(style.font.b);
|
assert!(style.font.b);
|
||||||
assert!(!style.font.i);
|
assert!(!style.font.i);
|
||||||
}
|
}
|
||||||
@@ -256,15 +234,15 @@ fn test_copy_cell_style() {
|
|||||||
fn test_get_cell_style_index() {
|
fn test_get_cell_style_index() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
assert_eq!(style_index, 0);
|
assert_eq!(style_index, 0);
|
||||||
assert!(!style.font.b);
|
assert!(!style.font.b);
|
||||||
|
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
|
|
||||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
assert_eq!(style_index, 1);
|
assert_eq!(style_index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,29 +250,29 @@ fn test_get_cell_style_index() {
|
|||||||
fn test_model_set_cells_with_values_styles() {
|
fn test_model_set_cells_with_values_styles() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
// Inputs
|
// Inputs
|
||||||
model.set_user_input(0, 1, 1, "21".to_string()).unwrap(); // A1
|
model.set_user_input(0, 1, 1, "21".to_string()); // A1
|
||||||
model.set_user_input(0, 2, 1, "2".to_string()).unwrap(); // A2
|
model.set_user_input(0, 2, 1, "2".to_string()); // A2
|
||||||
|
|
||||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
assert_eq!(style_index, 0);
|
assert_eq!(style_index, 0);
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
|
||||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
assert_eq!(style_index, 1);
|
assert_eq!(style_index, 1);
|
||||||
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 2, 1);
|
||||||
assert_eq!(style_index, 1);
|
assert_eq!(style_index, 1);
|
||||||
|
|
||||||
model.update_cell_with_number(0, 1, 2, 1.0).unwrap();
|
model.update_cell_with_number(0, 1, 2, 1.0);
|
||||||
model.update_cell_with_number(0, 2, 1, 2.0).unwrap();
|
model.update_cell_with_number(0, 2, 1, 2.0);
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
// Styles are not modified
|
// Styles are not modified
|
||||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
assert_eq!(style_index, 1);
|
assert_eq!(style_index, 1);
|
||||||
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
let style_index = model.get_cell_style_index(0, 2, 1);
|
||||||
assert_eq!(style_index, 1);
|
assert_eq!(style_index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,20 +280,20 @@ fn test_model_set_cells_with_values_styles() {
|
|||||||
fn test_style_fmt_id() {
|
fn test_style_fmt_id() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
style.num_fmt = "#.##".to_string();
|
style.num_fmt = "#.##".to_string();
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert_eq!(style.num_fmt, "#.##");
|
assert_eq!(style.num_fmt, "#.##");
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 10, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 10, 1);
|
||||||
style.num_fmt = "$$#,##0.0000".to_string();
|
style.num_fmt = "$$#,##0.0000".to_string();
|
||||||
assert!(model.set_cell_style(0, 10, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 10, 1, &style).is_ok());
|
||||||
let style = model.get_style_for_cell(0, 10, 1).unwrap();
|
let style = model.get_style_for_cell(0, 10, 1);
|
||||||
assert_eq!(style.num_fmt, "$$#,##0.0000");
|
assert_eq!(style.num_fmt, "$$#,##0.0000");
|
||||||
|
|
||||||
// Make sure old style is not touched
|
// Make sure old style is not touched
|
||||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert_eq!(style.num_fmt, "#.##");
|
assert_eq!(style.num_fmt, "#.##");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,13 +357,9 @@ fn set_input_autocomplete() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "1");
|
model._set("A1", "1");
|
||||||
model._set("A2", "2");
|
model._set("A2", "2");
|
||||||
model
|
model.set_user_input(0, 3, 1, "=SUM(A1:A2".to_string());
|
||||||
.set_user_input(0, 3, 1, "=SUM(A1:A2".to_string())
|
|
||||||
.unwrap();
|
|
||||||
// This will fail anyway
|
// This will fail anyway
|
||||||
model
|
model.set_user_input(0, 4, 1, "=SUM(A1*".to_string());
|
||||||
.set_user_input(0, 4, 1, "=SUM(A1*".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_formula("A3"), "=SUM(A1:A2)");
|
assert_eq!(model._get_formula("A3"), "=SUM(A1:A2)");
|
||||||
@@ -431,17 +405,17 @@ fn test_get_formatted_cell_value() {
|
|||||||
model._set("A5", "123.456");
|
model._set("A5", "123.456");
|
||||||
|
|
||||||
// change A5 format
|
// change A5 format
|
||||||
let mut style = model.get_style_for_cell(0, 5, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 5, 1);
|
||||||
style.num_fmt = "$#,##0.00".to_string();
|
style.num_fmt = "$#,##0.00".to_string();
|
||||||
model.set_cell_style(0, 5, 1, &style).unwrap();
|
model.set_cell_style(0, 5, 1, &style).unwrap();
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "foobar");
|
assert_eq!(model.formatted_cell_value(0, 1, 1).unwrap(), "foobar");
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
|
assert_eq!(model.formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "");
|
assert_eq!(model.formatted_cell_value(0, 3, 1).unwrap(), "");
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "123.456");
|
assert_eq!(model.formatted_cell_value(0, 4, 1).unwrap(), "123.456");
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
|
assert_eq!(model.formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -452,20 +426,20 @@ fn test_cell_formula() {
|
|||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 1, 1), // A1
|
model.cell_formula(0, 1, 1), // A1
|
||||||
Ok(Some("=1+2+3".to_string())),
|
Ok(Some("=1+2+3".to_string())),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 2, 1), // A2
|
model.cell_formula(0, 2, 1), // A2
|
||||||
Ok(None),
|
Ok(None),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 3, 1), // A3 - empty cell
|
model.cell_formula(0, 3, 1), // A3 - empty cell
|
||||||
Ok(None),
|
Ok(None),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(42, 1, 1),
|
model.cell_formula(42, 1, 1),
|
||||||
Err("Invalid sheet index".to_string()),
|
Err("Invalid sheet index".to_string()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -479,16 +453,16 @@ fn test_xlfn() {
|
|||||||
model.evaluate();
|
model.evaluate();
|
||||||
// Only modern formulas strip the '_xlfn.'
|
// Only modern formulas strip the '_xlfn.'
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 1, 1).unwrap(),
|
model.cell_formula(0, 1, 1).unwrap(),
|
||||||
Some("=_xlfn.SIN(1)".to_string())
|
Some("=_xlfn.SIN(1)".to_string())
|
||||||
);
|
);
|
||||||
// unknown formulas keep the '_xlfn.' prefix
|
// unknown formulas keep the '_xlfn.' prefix
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 2, 1).unwrap(),
|
model.cell_formula(0, 2, 1).unwrap(),
|
||||||
Some("=_xlfn.SINY(1)".to_string())
|
Some("=_xlfn.SINY(1)".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 3, 1).unwrap(),
|
model.cell_formula(0, 3, 1).unwrap(),
|
||||||
Some("=CONCAT(3,4)".to_string())
|
Some("=CONCAT(3,4)".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -500,11 +474,11 @@ fn test_letter_case() {
|
|||||||
model._set("A2", "=sIn(2)");
|
model._set("A2", "=sIn(2)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 1, 1).unwrap(),
|
model.cell_formula(0, 1, 1).unwrap(),
|
||||||
Some("=SIN(1)".to_string())
|
Some("=SIN(1)".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_cell_formula(0, 2, 1).unwrap(),
|
model.cell_formula(0, 2, 1).unwrap(),
|
||||||
Some("=SIN(2)".to_string())
|
Some("=SIN(2)".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ use crate::test::util::new_empty_model;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_formulas() {
|
fn test_formulas() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "$100.348".to_string());
|
||||||
.set_user_input(0, 1, 1, "$100.348".to_string())
|
model.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string());
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::test::util::new_empty_model;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_metadata_new_model() {
|
fn test_metadata_new_model() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "5.5".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "5.5".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
let metadata = &model.workbook.metadata;
|
let metadata = &model.workbook.metadata;
|
||||||
assert_eq!(metadata.application, "IronCalc Sheets");
|
assert_eq!(metadata.application, "IronCalc Sheets");
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_all_non_existing_sheet() {
|
fn test_delete_cell_non_existing_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_clear_all(13, 1, 1),
|
model.delete_cell(13, 1, 1),
|
||||||
Err("Invalid sheet index".to_string())
|
Err("Invalid sheet index".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_all_unset_cell() {
|
fn test_delete_cell_unset_cell() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert!(model.cell_clear_all(0, 1, 1).is_ok());
|
assert!(model.delete_cell(0, 1, 1).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_all_with_value() {
|
fn test_delete_cell_with_value() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "hello");
|
model._set("A1", "hello");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
@@ -25,7 +25,7 @@ fn test_cell_clear_all_with_value() {
|
|||||||
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
|
|
||||||
model.cell_clear_all(0, 1, 1).unwrap();
|
model.delete_cell(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -33,7 +33,7 @@ fn test_cell_clear_all_with_value() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_all_referenced_elsewhere() {
|
fn test_delete_cell_referenced_elsewhere() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "35");
|
model._set("A1", "35");
|
||||||
model._set("A2", "=2*A1");
|
model._set("A2", "=2*A1");
|
||||||
@@ -44,7 +44,7 @@ fn test_cell_clear_all_referenced_elsewhere() {
|
|||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
||||||
|
|
||||||
model.cell_clear_all(0, 1, 1).unwrap();
|
model.delete_cell(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -14,11 +14,9 @@ fn test_is_empty_cell_non_existing_sheet() {
|
|||||||
fn test_is_empty_cell() {
|
fn test_is_empty_cell() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
||||||
model
|
model.set_user_input(0, 3, 1, "Hello World".to_string());
|
||||||
.set_user_input(0, 3, 1, "Hello World".to_string())
|
|
||||||
.unwrap();
|
|
||||||
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
|
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
|
||||||
model.cell_clear_contents(0, 3, 1).unwrap();
|
model.set_cell_empty(0, 3, 1).unwrap();
|
||||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_contents_non_existing_sheet() {
|
fn test_set_cell_empty_non_existing_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_clear_contents(13, 1, 1),
|
model.set_cell_empty(13, 1, 1),
|
||||||
Err("Invalid sheet index".to_string())
|
Err("Invalid sheet index".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_contents_unset_cell() {
|
fn test_set_cell_empty_unset_cell() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.cell_clear_contents(0, 1, 1).unwrap();
|
model.set_cell_empty(0, 1, 1).unwrap();
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_contents_with_value() {
|
fn test_set_cell_empty_with_value() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "hello");
|
model._set("A1", "hello");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
@@ -28,7 +28,7 @@ fn test_cell_clear_contents_with_value() {
|
|||||||
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
|
|
||||||
model.cell_clear_contents(0, 1, 1).unwrap();
|
model.set_cell_empty(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -36,7 +36,7 @@ fn test_cell_clear_contents_with_value() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cell_clear_contents_referenced_elsewhere() {
|
fn test_set_cell_empty_referenced_elsewhere() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "35");
|
model._set("A1", "35");
|
||||||
model._set("A2", "=2*A1");
|
model._set("A2", "=2*A1");
|
||||||
@@ -47,7 +47,7 @@ fn test_cell_clear_contents_referenced_elsewhere() {
|
|||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
||||||
|
|
||||||
model.cell_clear_contents(0, 1, 1).unwrap();
|
model.set_cell_empty(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -57,12 +57,12 @@ fn test_quote_prefix_enter() {
|
|||||||
model._set("A2", "=ISTEXT(A1)");
|
model._set("A2", "=ISTEXT(A1)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
// We introduce a value with a "quote prefix" index
|
// We introduce a value with a "quote prefix" index
|
||||||
model.set_user_input(0, 1, 3, "'=A1".to_string()).unwrap();
|
model.set_user_input(0, 1, 3, "'=A1".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("C1"), *"=A1");
|
assert_eq!(model._get_text("C1"), *"=A1");
|
||||||
|
|
||||||
// But if we enter with a quote_prefix but without the "'" it won't be quote_prefix
|
// But if we enter with a quote_prefix but without the "'" it won't be quote_prefix
|
||||||
model.set_user_input(0, 1, 4, "=A1".to_string()).unwrap();
|
model.set_user_input(0, 1, 4, "=A1".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("D1"), *"123");
|
assert_eq!(model._get_text("D1"), *"123");
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ fn test_quote_prefix_reenter() {
|
|||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
// We introduce a value with a "quote prefix" index
|
// We introduce a value with a "quote prefix" index
|
||||||
model.set_user_input(0, 1, 1, "123".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "123".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ fn test_quote_prefix_reenter() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_cell_quote() {
|
fn test_update_cell_quote() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.update_cell_with_text(0, 1, 1, "= 1 + 3").unwrap();
|
model.update_cell_with_text(0, 1, 1, "= 1 + 3");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), *"= 1 + 3");
|
assert_eq!(model._get_text("A1"), *"= 1 + 3");
|
||||||
assert!(!model._has_formula("A1"));
|
assert!(!model._has_formula("A1"));
|
||||||
@@ -92,12 +92,12 @@ fn test_update_cell_quote() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_quote_prefix_reenter() {
|
fn test_update_quote_prefix_reenter() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
model.update_cell_with_text(0, 1, 1, "123");
|
||||||
model._set("A2", "=ISTEXT(A1)");
|
model._set("A2", "=ISTEXT(A1)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
// We reenter as a number
|
// We reenter as a number
|
||||||
model.update_cell_with_number(0, 1, 1, 123.0).unwrap();
|
model.update_cell_with_number(0, 1, 1, 123.0);
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||||
}
|
}
|
||||||
@@ -105,12 +105,12 @@ fn test_update_quote_prefix_reenter() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_quote_prefix_reenter_bool() {
|
fn test_update_quote_prefix_reenter_bool() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.update_cell_with_text(0, 1, 1, "TRUE").unwrap();
|
model.update_cell_with_text(0, 1, 1, "TRUE");
|
||||||
model._set("A2", "=ISTEXT(A1)");
|
model._set("A2", "=ISTEXT(A1)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
// We enter a bool
|
// We enter a bool
|
||||||
model.update_cell_with_bool(0, 1, 1, true).unwrap();
|
model.update_cell_with_bool(0, 1, 1, true);
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||||
}
|
}
|
||||||
@@ -118,29 +118,29 @@ fn test_update_quote_prefix_reenter_bool() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_quote_prefix_reenter_text() {
|
fn test_update_quote_prefix_reenter_text() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
model.update_cell_with_text(0, 1, 1, "123");
|
||||||
model._set("A2", "=ISTEXT(A1)");
|
model._set("A2", "=ISTEXT(A1)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||||
// We enter a string
|
// We enter a string
|
||||||
model.update_cell_with_text(0, 1, 1, "Hello").unwrap();
|
model.update_cell_with_text(0, 1, 1, "Hello");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
assert!(!model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
assert!(!model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_quote_prefix_reenter_text_2() {
|
fn test_update_quote_prefix_reenter_text_2() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
model.update_cell_with_text(0, 1, 1, "123");
|
||||||
model._set("A2", "=ISTEXT(A1)");
|
model._set("A2", "=ISTEXT(A1)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||||
// We enter another number
|
// We enter another number
|
||||||
model.update_cell_with_text(0, 1, 1, "42").unwrap();
|
model.update_cell_with_text(0, 1, 1, "42");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,449 +0,0 @@
|
|||||||
use crate::{expressions::token, test::util::new_empty_model, types::Cell};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_cell_with_text() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.set_user_input(0, 1, 1, "Hello".to_string()).unwrap();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.update_cell_with_text(1, 1, 1, "new value");
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.update_cell_with_text(0, 0, 1, "new value");
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.update_cell_with_text(0, 1, 1048579, "new value");
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_cell_with_number() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_number(0, 1, 1, 10.0).unwrap();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.update_cell_with_number(1, 1, 1, 20.0);
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.update_cell_with_number(0, 0, 1, 20.0);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.update_cell_with_number(0, 1, 1048579, 20.0);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_cell_with_bool() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_bool(0, 1, 1, true).unwrap();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.update_cell_with_bool(1, 1, 1, false);
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.update_cell_with_bool(0, 0, 1, false);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.update_cell_with_bool(0, 1, 1048579, false);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_cell_with_formula() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_number(0, 1, 1, 10.0).unwrap();
|
|
||||||
model
|
|
||||||
.update_cell_with_formula(0, 1, 2, "=A1*2".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.update_cell_with_formula(1, 1, 2, "=A1*2".to_string());
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.update_cell_with_formula(0, 0, 2, "=A1*2".to_string());
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.update_cell_with_formula(0, 1, 1048579, "=A1*2".to_string());
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_set_user_input() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_number(0, 1, 1, 10.0).unwrap();
|
|
||||||
model.evaluate();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.set_user_input(1, 1, 2, "20.0".to_string());
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.set_user_input(0, 0, 2, "20.0".to_string());
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.set_user_input(0, 1, 1048579, "20.0".to_string());
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_style_for_cell() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_number(0, 1, 1, 10.0).unwrap();
|
|
||||||
model.evaluate();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.get_style_for_cell(1, 1, 2);
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// ATTENTION : get_cell_style_index tries to get cell using row and col
|
|
||||||
// if we invalid row or column is given, it will return index 0.
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.get_style_for_cell(0, 0, 2);
|
|
||||||
assert!(update_result.is_ok());
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.get_style_for_cell(0, 1, 1048579);
|
|
||||||
assert!(update_result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_cell_style_index() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Below are safe inputs
|
|
||||||
model.update_cell_with_number(0, 1, 1, 10.0).unwrap();
|
|
||||||
model.evaluate();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid sheet
|
|
||||||
let update_result = model.get_cell_style_index(1, 1, 2);
|
|
||||||
assert_eq!(update_result, Err("Invalid sheet index".to_string()));
|
|
||||||
|
|
||||||
// ATTENTION : get_cell_style_index tries to get cell using row and col
|
|
||||||
// if we invalid row or column is given, it will return index 0.
|
|
||||||
|
|
||||||
// Case2 : Invalid Row
|
|
||||||
let update_result = model.get_cell_style_index(0, 0, 2);
|
|
||||||
assert_eq!(update_result, Ok(0));
|
|
||||||
|
|
||||||
// Case3 : Invalid Column
|
|
||||||
let update_result = model.get_cell_style_index(0, 1, 1048579);
|
|
||||||
assert_eq!(update_result, Ok(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_update_cell() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result =
|
|
||||||
model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.update_cell(0, 1, Cell::new_number(10.0, 1));
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result =
|
|
||||||
model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.update_cell(1, 1048579, Cell::new_number(10.0, 1));
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_style() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_style(0, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_style(1, 1048579, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_with_formula() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_formula(0, 1, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_formula(1, 1048579, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_with_number() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_number(0, 1, 1.0, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_number(1, 1048579, 1.0, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_with_string() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_string(0, 1, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_string(1, 1048579, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_with_boolean() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_boolean(0, 1, true, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_boolean(1, 1048579, true, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_set_cell_with_error() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1: Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_error(0, 1, token::Error::ERROR, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.set_cell_with_error(1, 1048579, token::Error::ERROR, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_cell_clear_contents() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.update_cell(1, 1, Cell::new_number(10.0, 1))
|
|
||||||
.unwrap();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents(0, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents(1, 1048579);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Valid case
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents(1, 1);
|
|
||||||
assert_eq!(update_result, Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_worksheet_cell_clear_contents_with_style() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.update_cell(1, 1, Cell::new_number(10.0, 1))
|
|
||||||
.unwrap();
|
|
||||||
// Now testing all the possible error scenarios
|
|
||||||
|
|
||||||
// Case1 : Invalid Row
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents_with_style(0, 1, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case2 : Invalid Column
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents_with_style(1, 1048579, 1);
|
|
||||||
assert_eq!(update_result, Err("Incorrect row or column".to_string()));
|
|
||||||
|
|
||||||
// Case3 : Valid case
|
|
||||||
let update_result = model
|
|
||||||
.workbook
|
|
||||||
.worksheet_mut(0)
|
|
||||||
.unwrap()
|
|
||||||
.cell_clear_contents_with_style(1, 1, 1);
|
|
||||||
assert_eq!(update_result, Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workbook_styles_get_style_error_handling() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
|
|
||||||
// case 1 : Invalid index
|
|
||||||
assert_eq!(
|
|
||||||
model.workbook.styles.get_style(15),
|
|
||||||
Err("Invalid index provided".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workbook_styles_get_style_without_quote_prefix_error_handling() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// case 1 : Invalid index
|
|
||||||
assert_eq!(
|
|
||||||
model.workbook.styles.get_style_without_quote_prefix(15),
|
|
||||||
Err("Invalid index provided".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workbook_styles_get_style_with_format_error_handling() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// case 1 : Invalid index
|
|
||||||
assert_eq!(
|
|
||||||
model
|
|
||||||
.workbook
|
|
||||||
.styles
|
|
||||||
.get_style_with_format(15, "dummy_num_format"),
|
|
||||||
Err("Invalid index provided".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workbook_styles_get_style_with_quote_prefix_handling() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
|
|
||||||
// case 1 : Invalid index
|
|
||||||
assert_eq!(
|
|
||||||
model.workbook.styles.get_style_with_quote_prefix(15),
|
|
||||||
Err("Invalid index provided".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,28 +5,16 @@ use crate::{cell::CellValue, test::util::new_empty_model};
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_currencies() {
|
fn test_currencies() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "$100.348".to_string());
|
||||||
.set_user_input(0, 1, 1, "$100.348".to_string())
|
model.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string());
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 2, 1, "$ 100.348".to_string());
|
||||||
.set_user_input(0, 2, 1, "$ 100.348".to_string())
|
model.set_user_input(0, 2, 2, "=ISNUMBER(A2)".to_string());
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 2, 2, "=ISNUMBER(A2)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.set_user_input(0, 3, 1, "100$".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "100$".to_string());
|
||||||
model
|
model.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string());
|
||||||
.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 4, 1, "3.1415926$".to_string());
|
||||||
.set_user_input(0, 4, 1, "3.1415926$".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -55,9 +43,9 @@ fn test_currencies() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn scientific() {
|
fn scientific() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "3e-4".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "3e-4".to_string());
|
||||||
model.set_user_input(0, 2, 1, "5e-4$".to_string()).unwrap();
|
model.set_user_input(0, 2, 1, "5e-4$".to_string());
|
||||||
model.set_user_input(0, 3, 1, "6e-4%".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "6e-4%".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -73,13 +61,9 @@ fn scientific() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_percentage() {
|
fn test_percentage() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 10, 1, "50%".to_string()).unwrap();
|
model.set_user_input(0, 10, 1, "50%".to_string());
|
||||||
model
|
model.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string());
|
||||||
.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string())
|
model.set_user_input(0, 11, 1, "55.759%".to_string());
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 11, 1, "55.759%".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -97,8 +81,8 @@ fn test_percentage_ops() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "5%");
|
model._set("A1", "5%");
|
||||||
model._set("A2", "20%");
|
model._set("A2", "20%");
|
||||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
|
||||||
model.set_user_input(0, 4, 1, "=A1*A2".to_string()).unwrap();
|
model.set_user_input(0, 4, 1, "=A1*A2".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -109,19 +93,11 @@ fn test_percentage_ops() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_numbers() {
|
fn test_numbers() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "1,000,000".to_string());
|
||||||
.set_user_input(0, 1, 1, "1,000,000".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 20, 1, "50,123.549".to_string());
|
||||||
.set_user_input(0, 20, 1, "50,123.549".to_string())
|
model.set_user_input(0, 21, 1, "50,12.549".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 22, 1, "1,234567".to_string());
|
||||||
model
|
|
||||||
.set_user_input(0, 21, 1, "50,12.549".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 22, 1, "1,234567".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -155,7 +131,7 @@ fn test_numbers() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_negative_numbers() {
|
fn test_negative_numbers() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "-100".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "-100".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -168,19 +144,15 @@ fn test_negative_numbers() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_negative_currencies() {
|
fn test_negative_currencies() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "-$100".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "-$100".to_string());
|
||||||
model
|
model.set_user_input(0, 2, 1, "-$99.123".to_string());
|
||||||
.set_user_input(0, 2, 1, "-$99.123".to_string())
|
|
||||||
.unwrap();
|
|
||||||
// This is valid!
|
// This is valid!
|
||||||
model.set_user_input(0, 3, 1, "$-345".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "$-345".to_string());
|
||||||
|
|
||||||
model.set_user_input(0, 1, 2, "-200$".to_string()).unwrap();
|
model.set_user_input(0, 1, 2, "-200$".to_string());
|
||||||
model
|
model.set_user_input(0, 2, 2, "-92.689$".to_string());
|
||||||
.set_user_input(0, 2, 2, "-92.689$".to_string())
|
|
||||||
.unwrap();
|
|
||||||
// This is valid!
|
// This is valid!
|
||||||
model.set_user_input(0, 3, 2, "-22$".to_string()).unwrap();
|
model.set_user_input(0, 3, 2, "-22$".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -202,10 +174,8 @@ fn test_formulas() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "$100");
|
model._set("A1", "$100");
|
||||||
model._set("A2", "$200");
|
model._set("A2", "$200");
|
||||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
|
||||||
model
|
model.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string());
|
||||||
.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -228,9 +198,9 @@ fn test_product() {
|
|||||||
model._set("A2", "$5");
|
model._set("A2", "$5");
|
||||||
model._set("A3", "4");
|
model._set("A3", "4");
|
||||||
|
|
||||||
model.set_user_input(0, 1, 2, "=A1*A2".to_string()).unwrap();
|
model.set_user_input(0, 1, 2, "=A1*A2".to_string());
|
||||||
model.set_user_input(0, 2, 2, "=A1*A3".to_string()).unwrap();
|
model.set_user_input(0, 2, 2, "=A1*A3".to_string());
|
||||||
model.set_user_input(0, 3, 2, "=A1*3".to_string()).unwrap();
|
model.set_user_input(0, 3, 2, "=A1*3".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -246,12 +216,10 @@ fn test_division() {
|
|||||||
model._set("A2", "$5");
|
model._set("A2", "$5");
|
||||||
model._set("A3", "4");
|
model._set("A3", "4");
|
||||||
|
|
||||||
model.set_user_input(0, 1, 2, "=A1/A2".to_string()).unwrap();
|
model.set_user_input(0, 1, 2, "=A1/A2".to_string());
|
||||||
model.set_user_input(0, 2, 2, "=A1/A3".to_string()).unwrap();
|
model.set_user_input(0, 2, 2, "=A1/A3".to_string());
|
||||||
model.set_user_input(0, 3, 2, "=A1/2".to_string()).unwrap();
|
model.set_user_input(0, 3, 2, "=A1/2".to_string());
|
||||||
model
|
model.set_user_input(0, 4, 2, "=100/A2".to_string());
|
||||||
.set_user_input(0, 4, 2, "=100/A2".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -267,54 +235,54 @@ fn test_some_complex_examples() {
|
|||||||
// $3.00 / 2 = $1.50
|
// $3.00 / 2 = $1.50
|
||||||
model._set("A1", "$3.00");
|
model._set("A1", "$3.00");
|
||||||
model._set("A2", "2");
|
model._set("A2", "2");
|
||||||
model.set_user_input(0, 3, 1, "=A1/A2".to_string()).unwrap();
|
model.set_user_input(0, 3, 1, "=A1/A2".to_string());
|
||||||
|
|
||||||
// $3 / 2 = $1
|
// $3 / 2 = $1
|
||||||
model._set("B1", "$3");
|
model._set("B1", "$3");
|
||||||
model._set("B2", "2");
|
model._set("B2", "2");
|
||||||
model.set_user_input(0, 3, 2, "=B1/B2".to_string()).unwrap();
|
model.set_user_input(0, 3, 2, "=B1/B2".to_string());
|
||||||
|
|
||||||
// $5.00 * 25% = 25% * $5.00 = $1.25
|
// $5.00 * 25% = 25% * $5.00 = $1.25
|
||||||
model._set("C1", "$5.00");
|
model._set("C1", "$5.00");
|
||||||
model._set("C2", "25%");
|
model._set("C2", "25%");
|
||||||
model.set_user_input(0, 3, 3, "=C1*C2".to_string()).unwrap();
|
model.set_user_input(0, 3, 3, "=C1*C2".to_string());
|
||||||
model.set_user_input(0, 4, 3, "=C2*C1".to_string()).unwrap();
|
model.set_user_input(0, 4, 3, "=C2*C1".to_string());
|
||||||
|
|
||||||
// $5 * 75% = 75% * $5 = $1
|
// $5 * 75% = 75% * $5 = $1
|
||||||
model._set("D1", "$5");
|
model._set("D1", "$5");
|
||||||
model._set("D2", "75%");
|
model._set("D2", "75%");
|
||||||
model.set_user_input(0, 3, 4, "=D1*D2".to_string()).unwrap();
|
model.set_user_input(0, 3, 4, "=D1*D2".to_string());
|
||||||
model.set_user_input(0, 4, 4, "=D2*D1".to_string()).unwrap();
|
model.set_user_input(0, 4, 4, "=D2*D1".to_string());
|
||||||
|
|
||||||
// $10 + $9.99 = $9.99 + $10 = $19.99
|
// $10 + $9.99 = $9.99 + $10 = $19.99
|
||||||
model._set("E1", "$10");
|
model._set("E1", "$10");
|
||||||
model._set("E2", "$9.99");
|
model._set("E2", "$9.99");
|
||||||
model.set_user_input(0, 3, 5, "=E1+E2".to_string()).unwrap();
|
model.set_user_input(0, 3, 5, "=E1+E2".to_string());
|
||||||
model.set_user_input(0, 4, 5, "=E2+E1".to_string()).unwrap();
|
model.set_user_input(0, 4, 5, "=E2+E1".to_string());
|
||||||
|
|
||||||
// $2 * 2 = 2 * $2 = $4
|
// $2 * 2 = 2 * $2 = $4
|
||||||
model._set("F1", "$2");
|
model._set("F1", "$2");
|
||||||
model._set("F2", "2");
|
model._set("F2", "2");
|
||||||
model.set_user_input(0, 3, 6, "=F1*F2".to_string()).unwrap();
|
model.set_user_input(0, 3, 6, "=F1*F2".to_string());
|
||||||
model.set_user_input(0, 4, 6, "=F2*F1".to_string()).unwrap();
|
model.set_user_input(0, 4, 6, "=F2*F1".to_string());
|
||||||
|
|
||||||
// $2.50 * 2 = 2 * $2.50 = $5.00
|
// $2.50 * 2 = 2 * $2.50 = $5.00
|
||||||
model._set("G1", "$2.50");
|
model._set("G1", "$2.50");
|
||||||
model._set("G2", "2");
|
model._set("G2", "2");
|
||||||
model.set_user_input(0, 3, 7, "=G1*G2".to_string()).unwrap();
|
model.set_user_input(0, 3, 7, "=G1*G2".to_string());
|
||||||
model.set_user_input(0, 4, 7, "=G2*G1".to_string()).unwrap();
|
model.set_user_input(0, 4, 7, "=G2*G1".to_string());
|
||||||
|
|
||||||
// $2 * 2.5 = 2.5 * $2 = $5
|
// $2 * 2.5 = 2.5 * $2 = $5
|
||||||
model._set("H1", "$2");
|
model._set("H1", "$2");
|
||||||
model._set("H2", "2.5");
|
model._set("H2", "2.5");
|
||||||
model.set_user_input(0, 3, 8, "=H1*H2".to_string()).unwrap();
|
model.set_user_input(0, 3, 8, "=H1*H2".to_string());
|
||||||
model.set_user_input(0, 4, 8, "=H2*H1".to_string()).unwrap();
|
model.set_user_input(0, 4, 8, "=H2*H1".to_string());
|
||||||
|
|
||||||
// 10% * 1,000 = 1,000 * 10% = 100
|
// 10% * 1,000 = 1,000 * 10% = 100
|
||||||
model._set("I1", "10%");
|
model._set("I1", "10%");
|
||||||
model._set("I2", "1,000");
|
model._set("I2", "1,000");
|
||||||
model.set_user_input(0, 3, 9, "=I1*I2".to_string()).unwrap();
|
model.set_user_input(0, 3, 9, "=I1*I2".to_string());
|
||||||
model.set_user_input(0, 4, 9, "=I2*I1".to_string()).unwrap();
|
model.set_user_input(0, 4, 9, "=I2*I1".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -352,15 +320,9 @@ fn test_financial_functions() {
|
|||||||
model._set("A3", "10");
|
model._set("A3", "10");
|
||||||
model._set("A4", "$10,000");
|
model._set("A4", "$10,000");
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 5, 1, "=PMT(A2/12,A3,A4)".to_string());
|
||||||
.set_user_input(0, 5, 1, "=PMT(A2/12,A3,A4)".to_string())
|
model.set_user_input(0, 6, 1, "=PMT(A2/12,A3,A4,,1)".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 7, 1, "=PMT(0.2, 3, -200)".to_string());
|
||||||
model
|
|
||||||
.set_user_input(0, 6, 1, "=PMT(A2/12,A3,A4,,1)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 7, 1, "=PMT(0.2, 3, -200)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -377,15 +339,9 @@ fn test_sum_function() {
|
|||||||
model._set("A1", "$100");
|
model._set("A1", "$100");
|
||||||
model._set("A2", "$300");
|
model._set("A2", "$300");
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||||
.set_user_input(0, 1, 2, "=SUM(A:A)".to_string())
|
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
|
||||||
model
|
|
||||||
.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -397,7 +353,7 @@ fn test_sum_function() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_number() {
|
fn test_number() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_user_input(0, 1, 1, "3".to_string()).unwrap();
|
model.set_user_input(0, 1, 1, "3".to_string());
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -411,9 +367,7 @@ fn test_number() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_currencies_eur_prefix() {
|
fn test_currencies_eur_prefix() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "€100.348".to_string());
|
||||||
.set_user_input(0, 1, 1, "€100.348".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -427,27 +381,19 @@ fn test_currencies_eur_prefix() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_currencies_eur_suffix() {
|
fn test_currencies_eur_suffix() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "100.348€".to_string());
|
||||||
.set_user_input(0, 1, 1, "100.348€".to_string())
|
model.set_user_input(0, 2, 1, "25€".to_string());
|
||||||
.unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "25€".to_string()).unwrap();
|
|
||||||
|
|
||||||
// negatives
|
// negatives
|
||||||
model
|
model.set_user_input(0, 1, 2, "-123.348€".to_string());
|
||||||
.set_user_input(0, 1, 2, "-123.348€".to_string())
|
model.set_user_input(0, 2, 2, "-42€".to_string());
|
||||||
.unwrap();
|
|
||||||
model.set_user_input(0, 2, 2, "-42€".to_string()).unwrap();
|
|
||||||
|
|
||||||
// with a space
|
// with a space
|
||||||
model
|
model.set_user_input(0, 1, 3, "101.348 €".to_string());
|
||||||
.set_user_input(0, 1, 3, "101.348 €".to_string())
|
model.set_user_input(0, 2, 3, "26 €".to_string());
|
||||||
.unwrap();
|
|
||||||
model.set_user_input(0, 2, 3, "26 €".to_string()).unwrap();
|
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 1, 4, "-12.348 €".to_string());
|
||||||
.set_user_input(0, 1, 4, "-12.348 €".to_string())
|
model.set_user_input(0, 2, 4, "-45 €".to_string());
|
||||||
.unwrap();
|
|
||||||
model.set_user_input(0, 2, 4, "-45 €".to_string()).unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -503,15 +449,9 @@ fn test_sum_function_eur() {
|
|||||||
model._set("A1", "€100");
|
model._set("A1", "€100");
|
||||||
model._set("A2", "€300");
|
model._set("A2", "€300");
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||||
.set_user_input(0, 1, 2, "=SUM(A:A)".to_string())
|
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
|
||||||
.unwrap();
|
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
|
||||||
model
|
|
||||||
.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -523,9 +463,7 @@ fn test_sum_function_eur() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn input_dates() {
|
fn input_dates() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model
|
model.set_user_input(0, 1, 1, "3/4/2025".to_string());
|
||||||
.set_user_input(0, 1, 1, "3/4/2025".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
@@ -536,9 +474,7 @@ fn input_dates() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// further date assignments do not change the format
|
// further date assignments do not change the format
|
||||||
model
|
model.set_user_input(0, 1, 1, "08-08-2028".to_string());
|
||||||
.set_user_input(0, 1, 1, "08-08-2028".to_string())
|
|
||||||
.unwrap();
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), "8/8/2028");
|
assert_eq!(model._get_text("A1"), "8/8/2028");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ fn test_sheet_markup() {
|
|||||||
model._set("A4", "Total");
|
model._set("A4", "Total");
|
||||||
model._set("B4", "=SUM(B2:B3)");
|
model._set("B4", "=SUM(B2:B3)");
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
model.set_cell_style(0, 1, 1, &style).unwrap();
|
model.set_cell_style(0, 1, 1, &style).unwrap();
|
||||||
model.set_cell_style(0, 1, 2, &style).unwrap();
|
model.set_cell_style(0, 1, 2, &style).unwrap();
|
||||||
model.set_cell_style(0, 4, 1, &style).unwrap();
|
model.set_cell_style(0, 4, 1, &style).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_sheet_markup(0),
|
model.sheet_markup(0),
|
||||||
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
|
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,14 +138,14 @@ fn test_insert_sheet() {
|
|||||||
|
|
||||||
// Insert the sheet at the end and check the formula
|
// Insert the sheet at the end and check the formula
|
||||||
assert!(model.insert_sheet("Bacchus", 1, None).is_ok());
|
assert!(model.insert_sheet("Bacchus", 1, None).is_ok());
|
||||||
model.set_user_input(1, 3, 1, "42".to_string()).unwrap();
|
model.set_user_input(1, 3, 1, "42".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), "42");
|
assert_eq!(model._get_text("A1"), "42");
|
||||||
assert_eq!(model._get_text("A2"), "#REF!");
|
assert_eq!(model._get_text("A2"), "#REF!");
|
||||||
|
|
||||||
// Insert a sheet in between the other two
|
// Insert a sheet in between the other two
|
||||||
assert!(model.insert_sheet("Dionysus", 1, None).is_ok());
|
assert!(model.insert_sheet("Dionysus", 1, None).is_ok());
|
||||||
model.set_user_input(1, 3, 1, "111".to_string()).unwrap();
|
model.set_user_input(1, 3, 1, "111".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), "42");
|
assert_eq!(model._get_text("A1"), "42");
|
||||||
assert_eq!(model._get_text("A2"), "111");
|
assert_eq!(model._get_text("A2"), "111");
|
||||||
@@ -176,7 +176,7 @@ fn test_rename_sheet() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.new_sheet();
|
model.new_sheet();
|
||||||
model._set("A1", "=NewSheet!A3");
|
model._set("A1", "=NewSheet!A3");
|
||||||
model.set_user_input(1, 3, 1, "25".to_string()).unwrap();
|
model.set_user_input(1, 3, 1, "25".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), "#REF!");
|
assert_eq!(model._get_text("A1"), "#REF!");
|
||||||
assert!(model.rename_sheet("Sheet2", "NewSheet").is_ok());
|
assert!(model.rename_sheet("Sheet2", "NewSheet").is_ok());
|
||||||
@@ -189,7 +189,7 @@ fn test_rename_sheet_by_index() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.new_sheet();
|
model.new_sheet();
|
||||||
model._set("A1", "=NewSheet!A1");
|
model._set("A1", "=NewSheet!A1");
|
||||||
model.set_user_input(1, 1, 1, "25".to_string()).unwrap();
|
model.set_user_input(1, 1, 1, "25".to_string());
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text("A1"), "#REF!");
|
assert_eq!(model._get_text("A1"), "#REF!");
|
||||||
assert!(model.rename_sheet_by_index(1, "NewSheet").is_ok());
|
assert!(model.rename_sheet_by_index(1, "NewSheet").is_ok());
|
||||||
@@ -236,11 +236,3 @@ fn test_delete_sheet_by_index() {
|
|||||||
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
|
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
|
||||||
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
|
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delete_sheet_error() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
model.new_sheet();
|
|
||||||
assert!(model.delete_sheet(2).is_err());
|
|
||||||
assert!(model.delete_sheet(1).is_ok());
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ use crate::types::Style;
|
|||||||
fn test_model_set_cells_with_values_styles() {
|
fn test_model_set_cells_with_values_styles() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
// Inputs
|
// Inputs
|
||||||
model.set_user_input(0, 1, 1, "21".to_string()).unwrap(); // A1
|
model.set_user_input(0, 1, 1, "21".to_string()); // A1
|
||||||
model.set_user_input(0, 2, 1, "42".to_string()).unwrap(); // A2
|
model.set_user_input(0, 2, 1, "42".to_string()); // A2
|
||||||
|
|
||||||
let style_base = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style_base = model.get_style_for_cell(0, 1, 1);
|
||||||
let mut style = style_base.clone();
|
let mut style = style_base.clone();
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
style.num_fmt = "#,##0.00".to_string();
|
style.num_fmt = "#,##0.00".to_string();
|
||||||
@@ -19,7 +19,7 @@ fn test_model_set_cells_with_values_styles() {
|
|||||||
let mut style = style_base;
|
let mut style = style_base;
|
||||||
style.num_fmt = "#,##0.00".to_string();
|
style.num_fmt = "#,##0.00".to_string();
|
||||||
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
|
||||||
let style: Style = model.get_style_for_cell(0, 2, 1).unwrap();
|
let style: Style = model.get_style_for_cell(0, 2, 1);
|
||||||
assert_eq!(style.num_fmt, "#,##0.00".to_string());
|
assert_eq!(style.num_fmt, "#,##0.00".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,21 +27,21 @@ fn test_model_set_cells_with_values_styles() {
|
|||||||
fn test_named_styles() {
|
fn test_named_styles() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "42");
|
model._set("A1", "42");
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||||
let bold_style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
let bold_style_index = model.get_cell_style_index(0, 1, 1);
|
||||||
let e = model
|
let e = model
|
||||||
.workbook
|
.workbook
|
||||||
.styles
|
.styles
|
||||||
.add_named_cell_style("bold", bold_style_index);
|
.add_named_cell_style("bold", bold_style_index);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
model._set("A2", "420");
|
model._set("A2", "420");
|
||||||
let a2_style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
let a2_style_index = model.get_cell_style_index(0, 2, 1);
|
||||||
assert!(a2_style_index != bold_style_index);
|
assert!(a2_style_index != bold_style_index);
|
||||||
let e = model.set_cell_style_by_name(0, 2, 1, "bold");
|
let e = model.set_cell_style_by_name(0, 2, 1, "bold");
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_cell_style_index(0, 2, 1), Ok(bold_style_index));
|
assert_eq!(model.get_cell_style_index(0, 2, 1), bold_style_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -49,7 +49,7 @@ fn test_create_named_style() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "42");
|
model._set("A1", "42");
|
||||||
|
|
||||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(!style.font.b);
|
assert!(!style.font.b);
|
||||||
|
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
@@ -59,6 +59,6 @@ fn test_create_named_style() {
|
|||||||
let e = model.set_cell_style_by_name(0, 1, 1, "bold");
|
let e = model.set_cell_style_by_name(0, 1, 1, "bold");
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
|
|
||||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
let style = model.get_style_for_cell(0, 1, 1);
|
||||||
assert!(style.font.b);
|
assert!(style.font.b);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::types::{Alignment, HorizontalAlignment, VerticalAlignment};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alignment_default() {
|
|
||||||
let alignment = Alignment::default();
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Bottom,
|
|
||||||
wrap_text: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let s = serde_json::to_string(&alignment).unwrap();
|
|
||||||
// defaults stringifies as an empty object
|
|
||||||
assert_eq!(s, "{}");
|
|
||||||
|
|
||||||
let a: Alignment = serde_json::from_str("{}").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(a, alignment)
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use crate::{test::util::new_empty_model, types::SheetProperties};
|
use crate::{test::util::new_empty_model, types::SheetInfo};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workbook_worksheets_info() {
|
fn workbook_worksheets_info() {
|
||||||
let model = new_empty_model();
|
let model = new_empty_model();
|
||||||
let sheets_info = model.get_worksheets_properties();
|
let sheets_info = model.workbook.get_worksheets_info();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sheets_info[0],
|
sheets_info[0],
|
||||||
SheetProperties {
|
SheetInfo {
|
||||||
name: "Sheet1".to_string(),
|
name: "Sheet1".to_string(),
|
||||||
state: "visible".to_string(),
|
state: "visible".to_string(),
|
||||||
sheet_id: 1,
|
sheet_id: 1,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ fn test_worksheet_dimension_single_cell() {
|
|||||||
fn test_worksheet_dimension_single_cell_set_empty() {
|
fn test_worksheet_dimension_single_cell_set_empty() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("W11", "1");
|
model._set("W11", "1");
|
||||||
model.cell_clear_contents(0, 11, 23).unwrap();
|
model.set_cell_empty(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -55,7 +55,7 @@ fn test_worksheet_dimension_single_cell_set_empty() {
|
|||||||
fn test_worksheet_dimension_single_cell_deleted() {
|
fn test_worksheet_dimension_single_cell_deleted() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("W11", "1");
|
model._set("W11", "1");
|
||||||
model.cell_clear_all(0, 11, 23).unwrap();
|
model.delete_cell(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -75,7 +75,7 @@ fn test_worksheet_dimension_multiple_cells() {
|
|||||||
model._set("AA17", "1");
|
model._set("AA17", "1");
|
||||||
model._set("G17", "1");
|
model._set("G17", "1");
|
||||||
model._set("B19", "1");
|
model._set("B19", "1");
|
||||||
model.cell_clear_all(0, 11, 23).unwrap();
|
model.delete_cell(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -100,9 +100,7 @@ fn test_worksheet_dimension_progressive() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 30, 50, "Hello World".to_string());
|
||||||
.set_user_input(0, 30, 50, "Hello World".to_string())
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -113,9 +111,7 @@ fn test_worksheet_dimension_progressive() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 10, 15, "Hello World".to_string());
|
||||||
.set_user_input(0, 10, 15, "Hello World".to_string())
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -126,9 +122,7 @@ fn test_worksheet_dimension_progressive() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 5, 25, "Hello World".to_string());
|
||||||
.set_user_input(0, 5, 25, "Hello World".to_string())
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -139,9 +133,7 @@ fn test_worksheet_dimension_progressive() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
model
|
model.set_user_input(0, 10, 250, "Hello World".to_string());
|
||||||
.set_user_input(0, 10, 250, "Hello World".to_string())
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
@@ -170,14 +162,12 @@ fn test_worksheet_navigate_to_edge_in_direction() {
|
|||||||
for (row_index, row) in inline_spreadsheet.into_iter().enumerate() {
|
for (row_index, row) in inline_spreadsheet.into_iter().enumerate() {
|
||||||
for (column_index, value) in row.into_iter().enumerate() {
|
for (column_index, value) in row.into_iter().enumerate() {
|
||||||
if value != 0 {
|
if value != 0 {
|
||||||
model
|
model.update_cell_with_number(
|
||||||
.update_cell_with_number(
|
0,
|
||||||
0,
|
(row_index as i32) + 1,
|
||||||
(row_index as i32) + 1,
|
(column_index as i32) + 1,
|
||||||
(column_index as i32) + 1,
|
value.into(),
|
||||||
value.into(),
|
);
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
mod test_add_delete_sheets;
|
|
||||||
mod test_autofill_columns;
|
|
||||||
mod test_autofill_rows;
|
|
||||||
mod test_border;
|
|
||||||
mod test_clear_cells;
|
|
||||||
mod test_diff_queue;
|
|
||||||
mod test_evaluation;
|
|
||||||
mod test_general;
|
|
||||||
mod test_grid_lines;
|
|
||||||
mod test_keyboard_navigation;
|
|
||||||
mod test_on_area_selection;
|
|
||||||
mod test_on_expand_selected_range;
|
|
||||||
mod test_on_paste_styles;
|
|
||||||
mod test_paste_csv;
|
|
||||||
mod test_rename_sheet;
|
|
||||||
mod test_row_column;
|
|
||||||
mod test_styles;
|
|
||||||
mod test_to_from_bytes;
|
|
||||||
mod test_undo_redo;
|
|
||||||
mod test_view;
|
|
||||||
mod test_window_size;
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{constants::DEFAULT_COLUMN_WIDTH, UserModel};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_undo_redo() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
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)
|
|
||||||
.unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
model.set_user_input(2, 1, 1, "=Sheet2!B1").unwrap();
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert!(model.get_formatted_cell_value(2, 1, 1).is_err());
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
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]
|
|
||||||
fn set_sheet_color() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_sheet_color(0, "#343434").unwrap();
|
|
||||||
let worksheets_properties = model.get_worksheets_properties();
|
|
||||||
assert_eq!(worksheets_properties.len(), 1);
|
|
||||||
assert_eq!(worksheets_properties[0].color, Some("#343434".to_owned()));
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_worksheets_properties()[0].color,
|
|
||||||
Some("#343434".to_owned())
|
|
||||||
);
|
|
||||||
// changes the color if there is one
|
|
||||||
model.set_sheet_color(0, "#2534FF").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_worksheets_properties()[0].color,
|
|
||||||
Some("#2534FF".to_owned())
|
|
||||||
);
|
|
||||||
// Setting it back to none
|
|
||||||
model.set_sheet_color(0, "").unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sheet_propagates() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
let worksheets_properties = model2.get_worksheets_properties();
|
|
||||||
assert_eq!(worksheets_properties.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delete_sheet_propagates() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
model.delete_sheet(0).unwrap();
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
let sheets_info = model2.get_worksheets_properties();
|
|
||||||
assert_eq!(sheets_info.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delete_last_sheet() {
|
|
||||||
// Deleting the last sheet, selects the previous
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
model.set_selected_sheet(2).unwrap();
|
|
||||||
model.delete_sheet(2).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_selected_sheet(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sheet_selects_it() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 1);
|
|
||||||
}
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
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() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// This is cell A3
|
|
||||||
model.set_user_input(0, 3, 1, "alpha").unwrap();
|
|
||||||
// We autofill from A3 to C3
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// B3
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 3, 2),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
// C3
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 3, 3),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn one_cell_right() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// B1
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 2),
|
|
||||||
Ok("23".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alpha_beta_gamma() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap(); // A1
|
|
||||||
model.set_user_input(0, 1, 2, "Bethe").unwrap(); // B1
|
|
||||||
model.set_user_input(0, 1, 3, "Gamow").unwrap(); // C1
|
|
||||||
model.set_user_input(0, 2, 1, "=A1").unwrap(); // A2
|
|
||||||
model.set_user_input(0, 2, 2, "=B1").unwrap(); // B2
|
|
||||||
model.set_user_input(0, 2, 3, "=C1").unwrap(); // C2
|
|
||||||
|
|
||||||
// We autofill from A1:C2 to I2
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 3,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// D1
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 6),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 5),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 6),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 2, 4), Ok("=D1".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn styles() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:C1
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
let b1 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 2,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let c1 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 3,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model.update_range_style(&b1, "font.i", "true").unwrap();
|
|
||||||
model
|
|
||||||
.update_range_style(&c1, "fill.bg_color", "#334455")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 3,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Check that cell E1 has B1 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 4), Ok("".to_string()));
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(!style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn left() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A12
|
|
||||||
model.set_user_input(0, 1, 10, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 1, 11, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 1, 12, "Gamow").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 10,
|
|
||||||
width: 3,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn left_4() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 1, 10, "Margaret Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 1, 11, "Geoffrey Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 1, 12, "Willy Fowler").unwrap();
|
|
||||||
model.set_user_input(0, 1, 13, "Fred Hoyle").unwrap();
|
|
||||||
|
|
||||||
// We fill left to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 10,
|
|
||||||
width: 4,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Willy Fowler".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
model.set_user_input(0, 1, 4, "Margaret Burbidge").unwrap();
|
|
||||||
|
|
||||||
// Invalid sheet
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 3,
|
|
||||||
row: 1,
|
|
||||||
column: 4,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid worksheet index: '3'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid column
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: -1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '-1'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid column
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN - 1,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16392'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW + 1,
|
|
||||||
column: 1,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048577'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW - 2,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048583'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 5,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
-10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-10'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_parameters() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 2,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Err("Invalid parameters for autofill".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
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() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// This is cell A3
|
|
||||||
model.set_user_input(0, 3, 1, "alpha").unwrap();
|
|
||||||
// We autofill from A3 to A5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn one_cell_down() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 1),
|
|
||||||
Ok("23".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alpha_beta_gamma() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "=A1").unwrap();
|
|
||||||
model.set_user_input(0, 2, 2, "=A2").unwrap();
|
|
||||||
model.set_user_input(0, 3, 2, "=A3").unwrap();
|
|
||||||
// We autofill from A1:B3 to A9
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 2,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 6, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 2),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 2),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 6, 2),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 2),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 2),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 2),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 2), Ok("=A4".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn styles() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
let a2 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 2,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let a3 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model.update_range_style(&a2, "font.i", "true").unwrap();
|
|
||||||
model
|
|
||||||
.update_range_style(&a3, "fill.bg_color", "#334455")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 1), Ok("".to_string()));
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(!style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn upwards() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A12
|
|
||||||
model.set_user_input(0, 10, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 11, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 12, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 10,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn upwards_4() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 10, 1, "Margaret Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 11, 1, "Geoffrey Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 12, 1, "Willy Fowler").unwrap();
|
|
||||||
model.set_user_input(0, 13, 1, "Fred Hoyle").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 10,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 4,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Willy Fowler".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 4, 1, "Margaret Burbidge").unwrap();
|
|
||||||
|
|
||||||
// Invalid sheet
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 3,
|
|
||||||
row: 4,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid worksheet index: '3'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid row
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: -1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-1'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid row
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW - 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048584'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN + 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16385'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN - 2,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16391'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
-10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-10'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_parameters() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Err("Invalid parameters for autofill".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,638 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
expressions::{types::Area, utils::number_to_column},
|
|
||||||
types::{Border, BorderItem, BorderStyle},
|
|
||||||
BorderArea, UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_all() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "All"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: Some(border_item.clone()),
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// let's check the borders around
|
|
||||||
{
|
|
||||||
let row = 4;
|
|
||||||
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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
let row = 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let column = 5;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).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);
|
|
||||||
}
|
|
||||||
let column = 9;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lets remove all of them:
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "None"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row in 4..10 {
|
|
||||||
for column in 5..10 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_inner() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Inner"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
// The inner part all have borders
|
|
||||||
for row in 6..8 {
|
|
||||||
for column in 7..8 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: Some(border_item.clone()),
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// F5 has border only left and bottom
|
|
||||||
{
|
|
||||||
// We check the border on F5
|
|
||||||
let style = model.get_cell_style(0, 5, 6).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
// It should be right and bottom
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: Some(border_item.clone()),
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Then let's try the bottom-right border
|
|
||||||
let style = model.get_cell_style(0, 8, 8).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
// It should be only left and top
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: None,
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_outer() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Outer"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
{
|
|
||||||
// We check the border on F5
|
|
||||||
let style = model.get_cell_style(0, 5, 6).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
// It should be only left and top
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: None,
|
|
||||||
top: Some(border_item),
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Then let's try the bottom-right border
|
|
||||||
let style = model.get_cell_style(0, 8, 8).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
// It should be only left and top
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: Some(border_item.clone()),
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
|
|
||||||
// let's check the borders around
|
|
||||||
{
|
|
||||||
let row = 4;
|
|
||||||
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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
let row = 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let column = 5;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).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);
|
|
||||||
}
|
|
||||||
let column = 9;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let border_item = BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#FF5566".to_string()),
|
|
||||||
};
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_top() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Top"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: Some(border_item.clone()),
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// let's check the borders around
|
|
||||||
{
|
|
||||||
let row = 4;
|
|
||||||
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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
let row = 9;
|
|
||||||
for column in 6..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let column = 5;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
let column = 9;
|
|
||||||
for row in 5..9 {
|
|
||||||
let style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
let expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_right() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Right"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row 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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_bottom() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Bottom"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: None,
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: Some(border_item.clone()),
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn borders_left() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
// We set an outer border in cells F5:H9
|
|
||||||
let range = &Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 6,
|
|
||||||
width: 3,
|
|
||||||
height: 4,
|
|
||||||
};
|
|
||||||
assert_eq!(number_to_column(6).unwrap(), "F");
|
|
||||||
assert_eq!(number_to_column(8).unwrap(), "H");
|
|
||||||
// ATM we don't have a way to create the object from Rust, that's ok.
|
|
||||||
let border_area: BorderArea = serde_json::from_str(
|
|
||||||
r##"{
|
|
||||||
"item": {
|
|
||||||
"style": "thin",
|
|
||||||
"color": "#FF5566"
|
|
||||||
},
|
|
||||||
"type": "Left"
|
|
||||||
}"##,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
model.set_area_with_border(range, &border_area).unwrap();
|
|
||||||
for row 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 expected_border = Border {
|
|
||||||
diagonal_up: false,
|
|
||||||
diagonal_down: false,
|
|
||||||
left: Some(border_item.clone()),
|
|
||||||
right: None,
|
|
||||||
top: None,
|
|
||||||
bottom: None,
|
|
||||||
diagonal: None,
|
|
||||||
};
|
|
||||||
assert_eq!(style.border, expected_border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{expressions::types::Area, UserModel};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 1, 1, "100$").unwrap();
|
|
||||||
model
|
|
||||||
.range_clear_contents(&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("100$".to_string())
|
|
||||||
);
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
|
|
||||||
model.set_user_input(0, 1, 1, "300").unwrap();
|
|
||||||
// clear contents keeps the formatting
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("300$".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.range_clear_all(&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("300$".to_string())
|
|
||||||
);
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
model.set_user_input(0, 1, 1, "400").unwrap();
|
|
||||||
// clear contents keeps the formatting
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("400".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clear_empty_cell() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model
|
|
||||||
.range_clear_contents(&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clear_all_empty_cell() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model
|
|
||||||
.range_clear_all(&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
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_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
|
||||||
let send_queue = model1.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 1, 2),
|
|
||||||
Ok("Hello IronCalc!".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_external_diffs_wrong_str() {
|
|
||||||
let mut model1 = UserModel::from_model(new_empty_model());
|
|
||||||
assert!(model1.apply_external_diffs("invalid".as_bytes()).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
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_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
|
||||||
assert!(model1.undo().is_ok());
|
|
||||||
assert!(model1.redo().is_ok());
|
|
||||||
let send_queue = model1.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 1, 2),
|
|
||||||
Ok("Hello IronCalc!".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn queue_undo_redo_multiple() {
|
|
||||||
let mut model1 = UserModel::from_model(new_empty_model());
|
|
||||||
|
|
||||||
// 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_user_input(0, 55, 55, "=42+8").unwrap();
|
|
||||||
|
|
||||||
for row in 1..5 {
|
|
||||||
model1.set_user_input(0, row, 17, "=ROW()").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
model1.insert_row(0, 3).unwrap();
|
|
||||||
model1.insert_row(0, 3).unwrap();
|
|
||||||
|
|
||||||
// undo al of them
|
|
||||||
while model1.can_undo() {
|
|
||||||
model1.undo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check it is an empty model
|
|
||||||
assert_eq!(model1.get_frozen_columns_count(0), Ok(0));
|
|
||||||
assert_eq!(model1.get_frozen_rows_count(0), Ok(0));
|
|
||||||
assert_eq!(model1.get_column_width(0, 7), Ok(DEFAULT_COLUMN_WIDTH));
|
|
||||||
assert_eq!(
|
|
||||||
model1.get_formatted_cell_value(0, 55, 55),
|
|
||||||
Ok("".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model1.get_row_height(0, 23), Ok(DEFAULT_ROW_HEIGHT));
|
|
||||||
assert_eq!(
|
|
||||||
model1.get_formatted_cell_value(0, 57, 55),
|
|
||||||
Ok("".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model1.get_row_height(0, 25), Ok(DEFAULT_ROW_HEIGHT));
|
|
||||||
|
|
||||||
// redo all of them
|
|
||||||
while model1.can_redo() {
|
|
||||||
model1.redo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// now send all this to a new model
|
|
||||||
let send_queue = model1.flush_send_queue();
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
// Check everything is as expected
|
|
||||||
assert_eq!(model2.get_frozen_columns_count(0), Ok(5));
|
|
||||||
assert_eq!(model2.get_frozen_rows_count(0), Ok(6));
|
|
||||||
assert_eq!(model2.get_column_width(0, 7), Ok(300.0));
|
|
||||||
// I inserted two rows
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 57, 55),
|
|
||||||
Ok("50".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model2.get_row_height(0, 25), Ok(123.0));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 1, 17),
|
|
||||||
Ok("1".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 2, 17),
|
|
||||||
Ok("2".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 3, 17),
|
|
||||||
Ok("".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 4, 17),
|
|
||||||
Ok("".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 5, 17),
|
|
||||||
Ok("5".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 6, 17),
|
|
||||||
Ok("6".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sheet() {
|
|
||||||
let mut model1 = UserModel::from_model(new_empty_model());
|
|
||||||
model1.new_sheet().unwrap();
|
|
||||||
model1.set_user_input(0, 1, 1, "42").unwrap();
|
|
||||||
model1.set_user_input(1, 1, 1, "=Sheet1!A1*2").unwrap();
|
|
||||||
|
|
||||||
let send_queue = model1.flush_send_queue();
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(1, 1, 1),
|
|
||||||
Ok("84".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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());
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn model_evaluates_automatically() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 1, 1, "=1 + 1").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pause_resume_evaluation() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.pause_evaluation();
|
|
||||||
model.set_user_input(0, 1, 1, "=1+1").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("#ERROR!".to_string())
|
|
||||||
);
|
|
||||||
model.evaluate();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
|
|
||||||
|
|
||||||
model.resume_evaluation();
|
|
||||||
model.set_user_input(0, 2, 1, "=1+4").unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 2, 1), Ok("5".to_string()));
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
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() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// Wrong sheet
|
|
||||||
assert!(model.set_user_input(1, 1, 1, "1").is_err());
|
|
||||||
// Wrong row
|
|
||||||
assert!(model.set_user_input(0, 0, 1, "1").is_err());
|
|
||||||
// Wrong column
|
|
||||||
assert!(model.set_user_input(0, 1, 0, "1").is_err());
|
|
||||||
// row too large
|
|
||||||
assert!(model.set_user_input(0, LAST_ROW, 1, "1").is_ok());
|
|
||||||
assert!(model.set_user_input(0, LAST_ROW + 1, 1, "1").is_err());
|
|
||||||
// column too large
|
|
||||||
assert!(model.set_user_input(0, 1, LAST_COLUMN, "1").is_ok());
|
|
||||||
assert!(model.set_user_input(0, 1, LAST_COLUMN + 1, "1").is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn user_model_debug_message() {
|
|
||||||
let model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let s = &format!("{:?}", model);
|
|
||||||
assert_eq!(s, "UserModel");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cell_type() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 1, 1, "1").unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "Wish you were here").unwrap();
|
|
||||||
model.set_user_input(0, 1, 3, "true").unwrap();
|
|
||||||
model.set_user_input(0, 1, 4, "=1/0").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_type(0, 1, 1).unwrap(), CellType::Number);
|
|
||||||
assert_eq!(model.get_cell_type(0, 1, 2).unwrap(), CellType::Text);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_cell_type(0, 1, 3).unwrap(),
|
|
||||||
CellType::LogicalValue
|
|
||||||
);
|
|
||||||
assert_eq!(model.get_cell_type(0, 1, 4).unwrap(), CellType::ErrorValue);
|
|
||||||
|
|
||||||
// empty cells are number type
|
|
||||||
assert_eq!(model.get_cell_type(0, 40, 40).unwrap(), CellType::Number);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insert_remove_rows() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let height = model.get_row_height(0, 5).unwrap();
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
// remove the row
|
|
||||||
assert!(model.delete_row(0, 5).is_ok());
|
|
||||||
// Row 5 has now the normal height
|
|
||||||
assert_eq!(model.get_row_height(0, 5), Ok(height));
|
|
||||||
// There is no value in A5
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 5, 1), Ok("".to_string()));
|
|
||||||
// Setting a value will not format it
|
|
||||||
assert!(model.set_user_input(0, 5, 1, "125").is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("125".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// undo twice
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
|
|
||||||
assert_eq!(model.get_row_height(0, 5), Ok(3.0 * height));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("100$".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn insert_remove_columns() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// column E
|
|
||||||
let column_width = model.get_column_width(0, 5).unwrap();
|
|
||||||
println!("{column_width}");
|
|
||||||
|
|
||||||
// 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_eq!(model.get_column_width(0, 5).unwrap(), 3.0 * column_width);
|
|
||||||
|
|
||||||
// remove the column
|
|
||||||
assert!(model.delete_column(0, 5).is_ok());
|
|
||||||
// Column 5 has now the normal width
|
|
||||||
assert_eq!(model.get_column_width(0, 5), Ok(column_width));
|
|
||||||
// There is no value in E5
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 5), Ok("".to_string()));
|
|
||||||
// Setting a value will not format it
|
|
||||||
assert!(model.set_user_input(0, 1, 5, "125").is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("125".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// undo twice (set_user_input and delete_column)
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
|
|
||||||
assert_eq!(model.get_column_width(0, 5), Ok(3.0 * column_width));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("100$".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delete_remove_cell() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let (sheet, row, column) = (0, 1, 1);
|
|
||||||
model.set_user_input(sheet, row, column, "100$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_and_set_name() {
|
|
||||||
let mut model = UserModel::new_empty("MyWorkbook123", "en", "UTC").unwrap();
|
|
||||||
assert_eq!(model.get_name(), "MyWorkbook123");
|
|
||||||
|
|
||||||
model.set_name("Another name");
|
|
||||||
assert_eq!(model.get_name(), "Another name");
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_tests() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
|
|
||||||
// default sheet has show_grid_lines = true
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
// default new sheet has show_grid_lines = true
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(true));
|
|
||||||
|
|
||||||
// wrong sheet number
|
|
||||||
assert_eq!(
|
|
||||||
model.get_show_grid_lines(2),
|
|
||||||
Err("Invalid sheet index".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// we can set it
|
|
||||||
model.set_show_grid_lines(1, false).unwrap();
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(false));
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(true));
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model2.get_show_grid_lines(1), Ok(false));
|
|
||||||
assert_eq!(model2.get_show_grid_lines(0), Ok(true));
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{
|
|
||||||
DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, DEFAULT_WINDOW_HEIGH, DEFAULT_WINDOW_WIDTH,
|
|
||||||
LAST_COLUMN,
|
|
||||||
},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_navigation() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_arrow_right().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 2, 1, 2]);
|
|
||||||
assert_eq!(view.column, 2);
|
|
||||||
assert_eq!(view.row, 1);
|
|
||||||
|
|
||||||
model.on_arrow_left().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 1]);
|
|
||||||
assert_eq!(view.column, 1);
|
|
||||||
assert_eq!(view.row, 1);
|
|
||||||
|
|
||||||
model.on_arrow_left().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 1]);
|
|
||||||
assert_eq!(view.column, 1);
|
|
||||||
assert_eq!(view.row, 1);
|
|
||||||
|
|
||||||
model.on_arrow_down().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [2, 1, 2, 1]);
|
|
||||||
assert_eq!(view.column, 1);
|
|
||||||
assert_eq!(view.row, 2);
|
|
||||||
|
|
||||||
model.on_arrow_up().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 1]);
|
|
||||||
assert_eq!(view.column, 1);
|
|
||||||
assert_eq!(view.row, 1);
|
|
||||||
|
|
||||||
model.on_arrow_up().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 1]);
|
|
||||||
assert_eq!(view.column, 1);
|
|
||||||
assert_eq!(view.row, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scroll_right() {
|
|
||||||
let window_width = DEFAULT_WINDOW_WIDTH as f64;
|
|
||||||
let column_width = DEFAULT_COLUMN_WIDTH;
|
|
||||||
let column_count = f64::floor(window_width / column_width) as i32;
|
|
||||||
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_arrow_right().unwrap();
|
|
||||||
|
|
||||||
model.set_selected_cell(3, column_count).unwrap();
|
|
||||||
model.on_arrow_right().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.left_column, 2);
|
|
||||||
|
|
||||||
model.on_arrow_right().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.left_column, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn last_colum() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_cell(3, LAST_COLUMN).unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.column, LAST_COLUMN);
|
|
||||||
|
|
||||||
model.on_arrow_right().unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.column, LAST_COLUMN);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn page_down() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let window_height = DEFAULT_WINDOW_HEIGH as f64;
|
|
||||||
let row_height = DEFAULT_ROW_HEIGHT;
|
|
||||||
let row_count = f64::floor(window_height / row_height) as i32;
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.row, 1 + row_count);
|
|
||||||
let scroll_y = model.get_scroll_y().unwrap();
|
|
||||||
assert_eq!(scroll_y, (row_count as f64) * DEFAULT_ROW_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we just test that page up and page down are inverse operations
|
|
||||||
#[test]
|
|
||||||
fn page_up() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
let row1 = model.get_selected_view().row;
|
|
||||||
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
let row2 = model.get_selected_view().row;
|
|
||||||
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
let row3 = model.get_selected_view().row;
|
|
||||||
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
|
|
||||||
model.on_page_up().unwrap();
|
|
||||||
assert_eq!(model.get_selected_view().row, row3);
|
|
||||||
|
|
||||||
model.on_page_up().unwrap();
|
|
||||||
assert_eq!(model.get_selected_view().row, row2);
|
|
||||||
|
|
||||||
model.on_page_up().unwrap();
|
|
||||||
assert_eq!(model.get_selected_view().row, row1);
|
|
||||||
|
|
||||||
model.on_page_up().unwrap();
|
|
||||||
assert_eq!(model.get_selected_view().row, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn page_up_fails_on_row1() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_arrow_up().unwrap();
|
|
||||||
assert_eq!(model.get_selected_view().row, 1);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_WINDOW_WIDTH},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_test() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
model.on_area_selecting(2, 4).unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 2, 4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this checks that is we select in the boundary we automatically scroll
|
|
||||||
#[test]
|
|
||||||
fn scroll_right() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let window_width = DEFAULT_WINDOW_WIDTH as f64;
|
|
||||||
let column_width = DEFAULT_COLUMN_WIDTH;
|
|
||||||
let column_count = f64::floor(window_width / column_width) as i32;
|
|
||||||
model.set_selected_cell(3, column_count).unwrap();
|
|
||||||
|
|
||||||
model.on_area_selecting(3, column_count + 3).unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [3, column_count, 3, column_count + 3]);
|
|
||||||
assert_eq!(view.left_column, 4);
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_WINDOW_WIDTH, LAST_COLUMN},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_right() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_expand_selected_range("ArrowRight").unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_right_decreases() {
|
|
||||||
// if the selected cell is on the upper right corner, right-arrow will decrease the size of teh area
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let (start_row, start_column, end_row, end_column) = (5, 3, 10, 8);
|
|
||||||
model.set_selected_cell(start_row, end_column).unwrap();
|
|
||||||
model
|
|
||||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.on_expand_selected_range("ArrowRight").unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(
|
|
||||||
view.range,
|
|
||||||
[start_row, start_column + 1, end_row, end_column]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_right_last_column() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_cell(1, LAST_COLUMN).unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, LAST_COLUMN, 1, LAST_COLUMN]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_right_scroll_right() {
|
|
||||||
let window_width = DEFAULT_WINDOW_WIDTH as f64;
|
|
||||||
let column_width = DEFAULT_COLUMN_WIDTH;
|
|
||||||
let column_count = f64::floor(window_width / column_width) as i32;
|
|
||||||
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
// initially the column to the left is A
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.left_column, 1);
|
|
||||||
|
|
||||||
// We select all columns from 1 to the last visible
|
|
||||||
let (start_row, start_column, end_row, end_column) = (1, 1, 1, column_count);
|
|
||||||
model.set_selected_cell(start_row, start_column).unwrap();
|
|
||||||
model
|
|
||||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Now we select one more column
|
|
||||||
model.on_expand_selected_range("ArrowRight").unwrap();
|
|
||||||
|
|
||||||
// The view has updated and the first visible column is B
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(
|
|
||||||
view.range,
|
|
||||||
[start_row, start_column, end_row, end_column + 1]
|
|
||||||
);
|
|
||||||
assert_eq!(view.left_column, 2);
|
|
||||||
|
|
||||||
// now we click on cell B2 and we
|
|
||||||
model.set_selected_cell(2, 2).unwrap();
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [2, 1, 2, 2]);
|
|
||||||
assert_eq!(view.left_column, 1);
|
|
||||||
|
|
||||||
// a second arrow left won't do anything
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [2, 1, 2, 2]);
|
|
||||||
assert_eq!(view.left_column, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_left() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_cell(5, 3).unwrap();
|
|
||||||
model.set_selected_range(5, 3, 10, 8).unwrap();
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [5, 3, 10, 7]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_left_left_border() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 1, 1, 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_left_increases() {
|
|
||||||
// If the selected cell is on the top right corner
|
|
||||||
// arrow left increases the selected area by
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
let (start_row, start_column, end_row, end_column) = (4, 10, 4, 20);
|
|
||||||
model.set_selected_cell(start_row, end_column).unwrap();
|
|
||||||
model
|
|
||||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
|
||||||
.unwrap();
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(
|
|
||||||
view.range,
|
|
||||||
[start_row, start_column - 1, end_row, end_column]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn arrow_left_scrolls_left() {
|
|
||||||
// If the selected cell is on the top right corner
|
|
||||||
// arrow left increases the selected area by
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
model.set_top_left_visible_cell(1, 50).unwrap();
|
|
||||||
|
|
||||||
model.set_selected_cell(1, 50).unwrap();
|
|
||||||
// arrow left x 2
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
model.on_expand_selected_range("ArrowLeft").unwrap();
|
|
||||||
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.range, [1, 48, 1, 50]);
|
|
||||||
assert_eq!(view.left_column, 48);
|
|
||||||
assert_eq!(view.column, 50);
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
use crate::types::Fill;
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_pasting() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let mut style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
style.fill = Fill {
|
|
||||||
pattern_type: "solid".to_string(),
|
|
||||||
fg_color: Some("#FF5577".to_string()),
|
|
||||||
bg_color: Some("#33FF44".to_string()),
|
|
||||||
};
|
|
||||||
let styles = vec![vec![style.clone()]];
|
|
||||||
|
|
||||||
model.set_selected_cell(5, 4).unwrap();
|
|
||||||
model.set_selected_range(5, 4, 10, 9).unwrap();
|
|
||||||
model.on_paste_styles(&styles).unwrap();
|
|
||||||
|
|
||||||
for row in 5..10 {
|
|
||||||
for column in 4..9 {
|
|
||||||
let cell_style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
assert_eq!(cell_style, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
let base_style = model.get_cell_style(0, 100, 100).unwrap();
|
|
||||||
|
|
||||||
for row in 5..10 {
|
|
||||||
for column in 4..9 {
|
|
||||||
let cell_style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
assert_eq!(cell_style, base_style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
for row in 5..10 {
|
|
||||||
for column in 4..9 {
|
|
||||||
let cell_style = model.get_cell_style(0, row, column).unwrap();
|
|
||||||
assert_eq!(cell_style, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
use crate::{expressions::types::Area, UserModel};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn csv_paste() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 7, 7, "=SUM(B4:D7)").unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 7, 7), Ok("0".to_string()));
|
|
||||||
|
|
||||||
// paste some numbers in B4:C7
|
|
||||||
let csv = "1,2,3\n4,5,6";
|
|
||||||
let area = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 4,
|
|
||||||
column: 2,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
model.set_selected_cell(4, 2).unwrap();
|
|
||||||
model.paste_csv_string(&area, csv).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 7),
|
|
||||||
Ok("21".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tsv_crlf_paste() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 7, 7, "=SUM(B4:D7)").unwrap();
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 7, 7), Ok("0".to_string()));
|
|
||||||
|
|
||||||
// paste some numbers in B4:C7
|
|
||||||
let csv = "1\t2\t3\r\n4\t5\t6";
|
|
||||||
let area = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 4,
|
|
||||||
column: 2,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
model.set_selected_cell(4, 2).unwrap();
|
|
||||||
model.paste_csv_string(&area, csv).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 7),
|
|
||||||
Ok("21".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cut_paste() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 1, 1, "42").unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "=A1*3+1").unwrap();
|
|
||||||
|
|
||||||
// set A1 bold
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.set_user_input(0, 2, 1, "A season of faith, \"perfection\"")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Select A1:B2 and copy
|
|
||||||
model.set_selected_range(1, 1, 2, 2).unwrap();
|
|
||||||
let copy = model.copy_to_clipboard().unwrap();
|
|
||||||
|
|
||||||
model.set_selected_cell(4, 4).unwrap();
|
|
||||||
|
|
||||||
// paste in cell D4 (4, 4)
|
|
||||||
model
|
|
||||||
.paste_from_clipboard((1, 1, 2, 2), ©.data, true)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 4), Ok("42".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 5), Ok("=D4*3+1".to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 5),
|
|
||||||
Ok("127".to_string())
|
|
||||||
);
|
|
||||||
// cell D4 must be bold
|
|
||||||
let style_d4 = model.get_cell_style(0, 4, 4).unwrap();
|
|
||||||
assert!(style_d4.font.b);
|
|
||||||
|
|
||||||
// range A1:B2 must be empty
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 2), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 2, 1), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 2, 2), Ok("".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn copy_paste_internal() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.set_user_input(0, 1, 1, "42").unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "=A1*3+1").unwrap();
|
|
||||||
|
|
||||||
// set A1 bold
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.set_user_input(0, 2, 1, "A season of faith, \"perfection\"")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Select A1:B2 and copy
|
|
||||||
model.set_selected_range(1, 1, 2, 2).unwrap();
|
|
||||||
let copy = model.copy_to_clipboard().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
copy.csv,
|
|
||||||
"42,127\n\"A season of faith, \"\"perfection\"\"\",\n"
|
|
||||||
);
|
|
||||||
assert_eq!(copy.range, (1, 1, 2, 2));
|
|
||||||
|
|
||||||
model.set_selected_cell(4, 4).unwrap();
|
|
||||||
|
|
||||||
// paste in cell D4 (4, 4)
|
|
||||||
model
|
|
||||||
.paste_from_clipboard((1, 1, 2, 2), ©.data, false)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 4), Ok("42".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 5), Ok("=D4*3+1".to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 5),
|
|
||||||
Ok("127".to_string())
|
|
||||||
);
|
|
||||||
// cell D4 must be bold
|
|
||||||
let style_d4 = model.get_cell_style(0, 4, 4).unwrap();
|
|
||||||
assert!(style_d4.font.b);
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 4), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 5), Ok("".to_string()));
|
|
||||||
// cell D4 must not be bold
|
|
||||||
let style_d4 = model.get_cell_style(0, 4, 4).unwrap();
|
|
||||||
assert!(!style_d4.font.b);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 4), Ok("42".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 5), Ok("=D4*3+1".to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 5),
|
|
||||||
Ok("127".to_string())
|
|
||||||
);
|
|
||||||
// cell D4 must be bold
|
|
||||||
let style_d4 = model.get_cell_style(0, 4, 4).unwrap();
|
|
||||||
assert!(style_d4.font.b);
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_rename() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.rename_sheet(0, "NewSheet").unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rename_with_same_name() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.rename_sheet(0, "Sheet1").unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].name, "Sheet1");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn undo_redo() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model.rename_sheet(0, "NewSheet").unwrap();
|
|
||||||
model.undo().unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].name, "Sheet1");
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.rename_sheet(0, ""),
|
|
||||||
Err("Invalid name for a sheet: ''.".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.rename_sheet(1, "Hello"),
|
|
||||||
Err("Invalid sheet index".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_insert_row() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let (sheet, column) = (0, 5);
|
|
||||||
for row in 1..5 {
|
|
||||||
assert!(model.set_user_input(sheet, row, column, "123").is_ok());
|
|
||||||
}
|
|
||||||
assert!(model.insert_row(sheet, 3).is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
|
|
||||||
"123"
|
|
||||||
);
|
|
||||||
assert!(model.redo().is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_insert_column() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let (sheet, row) = (0, 5);
|
|
||||||
for column in 1..5 {
|
|
||||||
assert!(model.set_user_input(sheet, row, column, "123").is_ok());
|
|
||||||
}
|
|
||||||
assert!(model.insert_column(sheet, 3).is_ok());
|
|
||||||
assert_eq!(model.get_formatted_cell_value(sheet, row, 3).unwrap(), "");
|
|
||||||
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(sheet, row, 3).unwrap(),
|
|
||||||
"123"
|
|
||||||
);
|
|
||||||
assert!(model.redo().is_ok());
|
|
||||||
assert_eq!(model.get_formatted_cell_value(sheet, row, 3).unwrap(), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_delete_column() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
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)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.delete_column(0, 5).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 2, 5), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_column_width(0, 5), Ok(DEFAULT_COLUMN_WIDTH));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 2, 5), Ok("6".to_string()));
|
|
||||||
assert_eq!(model.get_column_width(0, 5), Ok(DEFAULT_COLUMN_WIDTH * 3.0));
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 2, 5),
|
|
||||||
Ok("6".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_column_width(0, 5),
|
|
||||||
Ok(DEFAULT_COLUMN_WIDTH * 3.0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn delete_column_errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.delete_column(1, 1),
|
|
||||||
Err("Invalid sheet index".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.delete_column(0, 0),
|
|
||||||
Err("Column number '0' is not valid.".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.delete_column(0, LAST_COLUMN + 1),
|
|
||||||
Err("Column number '16385' is not valid.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(model.delete_column(0, LAST_COLUMN), Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_delete_row() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 15, 4, "3").unwrap();
|
|
||||||
model.set_user_input(0, 15, 6, "=D15*2").unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.set_row_height(0, 15, DEFAULT_ROW_HEIGHT * 3.0)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model.delete_row(0, 15).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 15, 6), Ok("".to_string()));
|
|
||||||
assert_eq!(model.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 15, 6),
|
|
||||||
Ok("6".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT * 3.0));
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 15, 6),
|
|
||||||
Ok("6".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model2.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT * 3.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_delete_row_no_style() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 15, 4, "3").unwrap();
|
|
||||||
model.set_user_input(0, 15, 6, "=D15*2").unwrap();
|
|
||||||
model.delete_row(0, 15).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 15, 6), Ok("".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn row_heigh_increases_automatically() {
|
|
||||||
let mut model = UserModel::new_empty("Workbook1", "en", "UTC").unwrap();
|
|
||||||
assert_eq!(model.get_row_height(0, 1), Ok(DEFAULT_ROW_HEIGHT));
|
|
||||||
|
|
||||||
// Entering a single line does not change the height
|
|
||||||
model
|
|
||||||
.set_user_input(0, 1, 1, "My home in Canada had horses")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_row_height(0, 1), Ok(DEFAULT_ROW_HEIGHT));
|
|
||||||
|
|
||||||
// entering a two liner does:
|
|
||||||
model
|
|
||||||
.set_user_input(0, 1, 1, "My home in Canada had horses\nAnd monkeys!")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(model.get_row_height(0, 1), Ok(2.0 * DEFAULT_ROW_HEIGHT));
|
|
||||||
}
|
|
||||||
@@ -1,725 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
expressions::types::Area,
|
|
||||||
types::{Alignment, BorderItem, BorderStyle, HorizontalAlignment, VerticalAlignment},
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_fonts() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
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()));
|
|
||||||
|
|
||||||
// bold
|
|
||||||
model.update_range_style(&range, "font.b", "true").unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(style.font.b);
|
|
||||||
|
|
||||||
// italics
|
|
||||||
model.update_range_style(&range, "font.i", "true").unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
|
|
||||||
// underline
|
|
||||||
model.update_range_style(&range, "font.u", "true").unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(style.font.u);
|
|
||||||
|
|
||||||
// strike
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "font.strike", "true")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(style.font.strike);
|
|
||||||
|
|
||||||
// color
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "font.color", "#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.font.color, Some("#F1F1F1".to_owned()));
|
|
||||||
|
|
||||||
while model.can_undo() {
|
|
||||||
model.undo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
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()));
|
|
||||||
|
|
||||||
while model.can_redo() {
|
|
||||||
model.redo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
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("#F1F1F1".to_owned()));
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
let style = model2.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("#F1F1F1".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn font_errors() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.b", "True"),
|
|
||||||
Err("Invalid value for boolean: 'True'.".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.i", "FALSE"),
|
|
||||||
Err("Invalid value for boolean: 'FALSE'.".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.bold", "true"),
|
|
||||||
Err("Invalid style path: 'font.bold'.".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.strike", ""),
|
|
||||||
Err("Invalid value for boolean: ''.".to_string())
|
|
||||||
);
|
|
||||||
// There is no cast for booleans
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.b", "1"),
|
|
||||||
Err("Invalid value for boolean: '1'.".to_string())
|
|
||||||
);
|
|
||||||
// colors don't work by name
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.color", "blue"),
|
|
||||||
Err("Invalid color: 'blue'.".to_string())
|
|
||||||
);
|
|
||||||
// No short form
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "font.color", "#FFF"),
|
|
||||||
Err("Invalid color: '#FFF'.".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_fill() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, None);
|
|
||||||
assert_eq!(style.fill.fg_color, None);
|
|
||||||
assert_eq!(&style.fill.pattern_type, "none");
|
|
||||||
|
|
||||||
// bg_color
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "fill.bg_color", "#F2F2F2")
|
|
||||||
.unwrap();
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "fill.fg_color", "#F3F4F5")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
|
||||||
assert_eq!(style.fill.fg_color, Some("#F3F4F5".to_owned()));
|
|
||||||
assert_eq!(&style.fill.pattern_type, "solid");
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
let style = model2.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
|
|
||||||
assert_eq!(style.fill.fg_color, Some("#F3F4F5".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fill_errors() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "fill.bg_color", "#FFF"),
|
|
||||||
Err("Invalid color: '#FFF'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "fill.fg_color", "#FFF"),
|
|
||||||
Err("Invalid color: '#FFF'.".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_format() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.num_fmt, "general");
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "num_fmt", "$#,##0.0000")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.num_fmt, "$#,##0.0000");
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.num_fmt, "general");
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.num_fmt, "$#,##0.0000");
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
let style = model2.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.num_fmt, "$#,##0.0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_borders() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "thin,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "thin,")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: None,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.right", "dotted,#F1F1F2")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.right,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Dotted,
|
|
||||||
color: Some("#F1F1F2".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.top", "double,#F1F1F3")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.top,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Double,
|
|
||||||
color: Some("#F1F1F3".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.bottom", "medium,#F1F1F4")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.bottom,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Medium,
|
|
||||||
color: Some("#F1F1F4".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
while model.can_undo() {
|
|
||||||
model.undo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.border.left, None);
|
|
||||||
assert_eq!(style.border.top, None);
|
|
||||||
assert_eq!(style.border.right, None);
|
|
||||||
assert_eq!(style.border.bottom, None);
|
|
||||||
|
|
||||||
while model.can_redo() {
|
|
||||||
model.redo().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: None,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.right,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Dotted,
|
|
||||||
color: Some("#F1F1F2".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.top,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Double,
|
|
||||||
color: Some("#F1F1F3".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.bottom,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Medium,
|
|
||||||
color: Some("#F1F1F4".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
|
|
||||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
let style = model2.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Thin,
|
|
||||||
color: None,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.right,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Dotted,
|
|
||||||
color: Some("#F1F1F2".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.top,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Double,
|
|
||||||
color: Some("#F1F1F3".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
style.border.bottom,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Medium,
|
|
||||||
color: Some("#F1F1F4".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_alignment() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(alignment, None);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.horizontal", "center")
|
|
||||||
.unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::Center,
|
|
||||||
vertical: VerticalAlignment::Bottom,
|
|
||||||
wrap_text: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.horizontal", "centerContinuous")
|
|
||||||
.unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::CenterContinuous,
|
|
||||||
vertical: VerticalAlignment::Bottom,
|
|
||||||
wrap_text: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 2,
|
|
||||||
column: 2,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.vertical", "distributed")
|
|
||||||
.unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Distributed,
|
|
||||||
wrap_text: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.vertical", "justify")
|
|
||||||
.unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Justify,
|
|
||||||
wrap_text: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model.update_range_style(&range, "alignment", "").unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
|
|
||||||
assert_eq!(alignment, None);
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Justify,
|
|
||||||
wrap_text: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alignment_errors() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment", "some"),
|
|
||||||
Err("Alignment must be empty, but found: 'some'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.vertical", "justified"),
|
|
||||||
Err("Invalid value for vertical alignment: 'justified'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.horizontal", "unjustified"),
|
|
||||||
Err("Invalid value for horizontal alignment: 'unjustified'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.vertical", "justify")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Also fail if there is an alignment
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment", "some"),
|
|
||||||
Err("Alignment must be empty, but found: 'some'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.vertical", "justified"),
|
|
||||||
Err("Invalid value for vertical alignment: 'justified'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.horizontal", "unjustified"),
|
|
||||||
Err("Invalid value for horizontal alignment: 'unjustified'.".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_wrap_text() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.wrap_text", "T"),
|
|
||||||
Err("Invalid value for boolean: 'T'.".to_string())
|
|
||||||
);
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "alignment.wrap_text", "true")
|
|
||||||
.unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Bottom,
|
|
||||||
wrap_text: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
model.undo().unwrap();
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(alignment, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
|
|
||||||
assert_eq!(
|
|
||||||
alignment,
|
|
||||||
Some(Alignment {
|
|
||||||
horizontal: HorizontalAlignment::General,
|
|
||||||
vertical: VerticalAlignment::Bottom,
|
|
||||||
wrap_text: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "alignment.wrap_text", "True"),
|
|
||||||
Err("Invalid value for boolean: 'True'.".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn more_basic_borders() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "thick,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::Thick,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "slantDashDot,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::SlantDashDot,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "mediumDashDot,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::MediumDashDot,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "mediumDashDotDot,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::MediumDashDotDot,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "mediumDashed,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::MediumDashed,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn border_errors() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "border.lef", "thick,#F1F1F1"),
|
|
||||||
Err("Invalid style path: 'border.lef'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "border.left", "thic,#F1F1F1"),
|
|
||||||
Err("Invalid border style: 'thic'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "border.left", "thick,#F1F1F"),
|
|
||||||
Err("Invalid color: '#F1F1F'.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "border.left", " "),
|
|
||||||
Err("Invalid border value: ' '.".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.update_range_style(&range, "border.left", "thick,#F1F1F1,thin"),
|
|
||||||
Err("Invalid border value: 'thick,#F1F1F1,thin'.".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_removes_border() {
|
|
||||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
|
||||||
let range = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
model
|
|
||||||
.update_range_style(&range, "border.left", "mediumDashDotDot,#F1F1F1")
|
|
||||||
.unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
style.border.left,
|
|
||||||
Some(BorderItem {
|
|
||||||
style: BorderStyle::MediumDashDotDot,
|
|
||||||
color: Some("#F1F1F1".to_owned()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
model.update_range_style(&range, "border.left", "").unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(style.border.left, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn false_removes_value() {
|
|
||||||
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();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(style.font.b);
|
|
||||||
|
|
||||||
model.update_range_style(&range, "font.b", "false").unwrap();
|
|
||||||
let style = model.get_cell_style(0, 1, 1).unwrap();
|
|
||||||
assert!(!style.font.b);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{test::util::new_empty_model, UserModel};
|
|
||||||
|
|
||||||
#[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_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
|
||||||
|
|
||||||
let model_bytes = model1.to_bytes();
|
|
||||||
|
|
||||||
let model2 = UserModel::from_bytes(&model_bytes).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
|
||||||
assert_eq!(
|
|
||||||
model2.get_formatted_cell_value(0, 1, 2),
|
|
||||||
Ok("Hello IronCalc!".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let model_bytes = "Early in the morning, late in the century, Cricklewood Broadway.".as_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
&UserModel::from_bytes(model_bytes).unwrap_err(),
|
|
||||||
"Error parsing workbook: invalid packing"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{test::util::new_empty_model, UserModel};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_undo_redo() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// at the beginning I cannot undo or redo
|
|
||||||
assert!(!model.can_undo());
|
|
||||||
assert!(!model.can_redo());
|
|
||||||
assert!(model.set_user_input(0, 1, 1, "=1+2").is_ok());
|
|
||||||
|
|
||||||
// Once I enter a value I can undo but not redo
|
|
||||||
assert!(model.can_undo());
|
|
||||||
assert!(!model.can_redo());
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("3".to_string()));
|
|
||||||
|
|
||||||
// If I undo, I can't undo anymore, but I can redo
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert!(!model.can_undo());
|
|
||||||
assert!(model.can_redo());
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
|
||||||
|
|
||||||
// If I redo, I have the old value and formula
|
|
||||||
assert!(model.redo().is_ok());
|
|
||||||
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("3".to_string()));
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+2".to_string()));
|
|
||||||
assert!(model.can_undo());
|
|
||||||
assert!(!model.can_redo());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn undo_redo_respect_styles() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert!(model.set_user_input(0, 1, 1, "100").is_ok());
|
|
||||||
assert!(model.set_user_input(0, 1, 1, "125$").is_ok());
|
|
||||||
// The content of the cell is just the number 125
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("125".to_string()));
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
// The cell has no currency number formatting
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("100".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("100".to_string()));
|
|
||||||
assert!(model.redo().is_ok());
|
|
||||||
// The cell has the number 125 formatted as '125$'
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 1),
|
|
||||||
Ok("125$".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 1), Ok("125".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_undo_can_redo() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert!(!model.can_undo());
|
|
||||||
assert!(!model.can_redo());
|
|
||||||
|
|
||||||
assert!(model.undo().is_ok());
|
|
||||||
assert!(model.redo().is_ok());
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{LAST_COLUMN, LAST_ROW},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
user_model::SelectedView,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn initial_view() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let model = UserModel::from_model(model);
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_the_cell_sets_the_range() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_cell(5, 4).unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 4,
|
|
||||||
range: [5, 4, 5, 4],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_the_range_does_not_set_the_cell() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(5, 4, 10, 6),
|
|
||||||
Err(
|
|
||||||
"The selected cells is not in one of the corners. Row: '1' and row range '(5, 10)'"
|
|
||||||
.to_string()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_new_sheet_and_back() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.new_sheet().unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 1);
|
|
||||||
model.set_selected_cell(5, 4).unwrap();
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
model.set_selected_sheet(1).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (1, 5, 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_selected_cell_errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(-5, 4),
|
|
||||||
Err("Invalid row: '-5'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(5, -4),
|
|
||||||
Err("Invalid column: '-4'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(-1, 1, 1, 1),
|
|
||||||
Err("Invalid row: '-1'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 0, 1, 1),
|
|
||||||
Err("Invalid column: '0'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 1, LAST_ROW + 1, 1),
|
|
||||||
Err("Invalid row: '1048577'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 1, 1, LAST_COLUMN + 1),
|
|
||||||
Err("Invalid column: '16385'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_selected_cell_errors_wrong_sheet() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// forcefully set a wrong index
|
|
||||||
model.workbook.views.get_mut(&0).unwrap().sheet = 2;
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// It's returning the wrong number
|
|
||||||
assert_eq!(model.get_selected_sheet(), 2);
|
|
||||||
|
|
||||||
// But we can't set the selected cell anymore
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(3, 4),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(3, 4, 5, 6),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(3, 4),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// we can fix it by setting the right cell
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
model.set_selected_cell(3, 4).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_visible_cell() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_top_left_visible_cell(100, 12).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 100,
|
|
||||||
left_column: 12
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let s = serde_json::to_string(&model.get_selected_view()).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::from_str::<SelectedView>(&s).unwrap(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 100,
|
|
||||||
left_column: 12
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_visible_cell_errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(-100, 12),
|
|
||||||
Err("Invalid row: '-100'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(100, -12),
|
|
||||||
Err("Invalid column: '-12'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors_no_views() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// forcefully remove the view
|
|
||||||
model.workbook.views = HashMap::new();
|
|
||||||
// also in the sheet
|
|
||||||
model.workbook.worksheets[0].views = HashMap::new();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// get methods will return defaults
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// set methods won't complain. but won't work either
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
model.set_selected_cell(5, 6).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{DEFAULT_ROW_HEIGHT, DEFAULT_WINDOW_HEIGH, DEFAULT_WINDOW_WIDTH},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_test() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
let window_height = model.get_window_height().unwrap();
|
|
||||||
assert_eq!(window_height, DEFAULT_WINDOW_HEIGH);
|
|
||||||
|
|
||||||
let window_width = model.get_window_width().unwrap();
|
|
||||||
assert_eq!(window_width, DEFAULT_WINDOW_WIDTH);
|
|
||||||
|
|
||||||
// Set the window height to double the default and check that page_down behaves as expected
|
|
||||||
model.set_window_height((window_height * 2) as f64);
|
|
||||||
model.on_page_down().unwrap();
|
|
||||||
|
|
||||||
let row_height = DEFAULT_ROW_HEIGHT;
|
|
||||||
let row_count = f64::floor((window_height * 2) as f64 / row_height) as i32;
|
|
||||||
let view = model.get_selected_view();
|
|
||||||
assert_eq!(view.row, 1 + row_count);
|
|
||||||
let scroll_y = model.get_scroll_y().unwrap();
|
|
||||||
assert_eq!(scroll_y, (row_count as f64) * DEFAULT_ROW_HEIGHT);
|
|
||||||
}
|
|
||||||
@@ -20,8 +20,7 @@ impl Model {
|
|||||||
let cell_reference = self._parse_reference(cell);
|
let cell_reference = self._parse_reference(cell);
|
||||||
let column = cell_reference.column;
|
let column = cell_reference.column;
|
||||||
let row = cell_reference.row;
|
let row = cell_reference.row;
|
||||||
self.set_user_input(cell_reference.sheet, row, column, value.to_string())
|
self.set_user_input(cell_reference.sheet, row, column, value.to_string());
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
pub fn _has_formula(&self, cell: &str) -> bool {
|
pub fn _has_formula(&self, cell: &str) -> bool {
|
||||||
self._get_formula_opt(cell).is_some()
|
self._get_formula_opt(cell).is_some()
|
||||||
@@ -33,11 +32,11 @@ impl Model {
|
|||||||
let cell_reference = self._parse_reference(cell);
|
let cell_reference = self._parse_reference(cell);
|
||||||
let column = cell_reference.column;
|
let column = cell_reference.column;
|
||||||
let row = cell_reference.row;
|
let row = cell_reference.row;
|
||||||
self.get_cell_formula(cell_reference.sheet, row, column)
|
self.cell_formula(cell_reference.sheet, row, column)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
pub fn _get_text_at(&self, sheet: u32, row: i32, column: i32) -> String {
|
pub fn _get_text_at(&self, sheet: u32, row: i32, column: i32) -> String {
|
||||||
self.get_formatted_cell_value(sheet, row, column).unwrap()
|
self.formatted_cell_value(sheet, row, column).unwrap()
|
||||||
}
|
}
|
||||||
pub fn _get_text(&self, cell: &str) -> String {
|
pub fn _get_text(&self, cell: &str) -> String {
|
||||||
let CellReferenceIndex { sheet, row, column } = self._parse_reference(cell);
|
let CellReferenceIndex { sheet, row, column } = self._parse_reference(cell);
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
use bitcode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, fmt::Display};
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
use crate::expressions::token::Error;
|
use crate::expressions::token::Error;
|
||||||
|
|
||||||
|
// Useful for `#[serde(default = "default_as_true")]`
|
||||||
|
fn default_as_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn default_as_false() -> bool {
|
fn default_as_false() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Useful for `#[serde(skip_serializing_if = "is_true")]`
|
||||||
|
fn is_true(b: &bool) -> bool {
|
||||||
|
*b
|
||||||
|
}
|
||||||
|
|
||||||
fn is_false(b: &bool) -> bool {
|
fn is_false(b: &bool) -> bool {
|
||||||
!*b
|
!*b
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
fn is_zero(num: &i32) -> bool {
|
||||||
|
*num == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_default_alignment(o: &Option<Alignment>) -> bool {
|
||||||
|
o.is_none() || *o == Some(Alignment::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hashmap_is_empty(h: &HashMap<String, Table>) -> bool {
|
||||||
|
h.values().len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub application: String,
|
pub application: String,
|
||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
@@ -22,25 +44,14 @@ pub struct Metadata {
|
|||||||
pub last_modified: String, //"2020-11-20T16:24:35"
|
pub last_modified: String, //"2020-11-20T16:24:35"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct WorkbookSettings {
|
pub struct WorkbookSettings {
|
||||||
pub tz: String,
|
pub tz: String,
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Workbook View tracks of the selected sheet for each view
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
|
||||||
pub struct WorkbookView {
|
|
||||||
/// The index of the currently selected sheet.
|
|
||||||
pub sheet: u32,
|
|
||||||
/// The current width of the window
|
|
||||||
pub window_width: i64,
|
|
||||||
/// The current heigh of the window
|
|
||||||
pub window_height: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An internal representation of an IronCalc Workbook
|
/// An internal representation of an IronCalc Workbook
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Clone)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Workbook {
|
pub struct Workbook {
|
||||||
pub shared_strings: Vec<String>,
|
pub shared_strings: Vec<String>,
|
||||||
pub defined_names: Vec<DefinedName>,
|
pub defined_names: Vec<DefinedName>,
|
||||||
@@ -49,22 +60,28 @@ pub struct Workbook {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub settings: WorkbookSettings,
|
pub settings: WorkbookSettings,
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(skip_serializing_if = "hashmap_is_empty")]
|
||||||
pub tables: HashMap<String, Table>,
|
pub tables: HashMap<String, Table>,
|
||||||
pub views: HashMap<u32, WorkbookView>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct DefinedName {
|
pub struct DefinedName {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub formula: String,
|
pub formula: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub sheet_id: Option<u32>,
|
pub sheet_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move to worksheet.rs make frozen_rows/columns private and u32
|
||||||
|
/// Internal representation of a worksheet Excel object
|
||||||
|
|
||||||
/// * state:
|
/// * state:
|
||||||
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
||||||
/// hidden, veryHidden, visible
|
/// hidden, veryHidden, visible
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum SheetState {
|
pub enum SheetState {
|
||||||
Visible,
|
Visible,
|
||||||
Hidden,
|
Hidden,
|
||||||
@@ -81,25 +98,8 @@ impl Display for SheetState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of the worksheet as seen by the user. This includes
|
|
||||||
/// details such as the currently selected cell, the visible range, and the
|
|
||||||
/// position of the viewport.
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
|
||||||
pub struct WorksheetView {
|
|
||||||
/// The row index of the currently selected cell.
|
|
||||||
pub row: i32,
|
|
||||||
/// The column index of the currently selected cell.
|
|
||||||
pub column: i32,
|
|
||||||
/// The selected range in the worksheet, specified as [start_row, start_column, end_row, end_column].
|
|
||||||
pub range: [i32; 4],
|
|
||||||
/// The row index of the topmost visible cell in the worksheet view.
|
|
||||||
pub top_row: i32,
|
|
||||||
/// The column index of the leftmost visible cell in the worksheet view.
|
|
||||||
pub left_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal representation of a worksheet Excel object
|
/// Internal representation of a worksheet Excel object
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Clone)]
|
||||||
pub struct Worksheet {
|
pub struct Worksheet {
|
||||||
pub dimension: String,
|
pub dimension: String,
|
||||||
pub cols: Vec<Col>,
|
pub cols: Vec<Col>,
|
||||||
@@ -109,14 +109,16 @@ pub struct Worksheet {
|
|||||||
pub shared_formulas: Vec<String>,
|
pub shared_formulas: Vec<String>,
|
||||||
pub sheet_id: u32,
|
pub sheet_id: u32,
|
||||||
pub state: SheetState,
|
pub state: SheetState,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
pub merge_cells: Vec<String>,
|
pub merge_cells: Vec<String>,
|
||||||
pub comments: Vec<Comment>,
|
pub comments: Vec<Comment>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(skip_serializing_if = "is_zero")]
|
||||||
pub frozen_rows: i32,
|
pub frozen_rows: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(skip_serializing_if = "is_zero")]
|
||||||
pub frozen_columns: i32,
|
pub frozen_columns: i32,
|
||||||
pub views: HashMap<u32, WorksheetView>,
|
|
||||||
/// Whether or not to show the grid lines in the worksheet
|
|
||||||
pub show_grid_lines: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal representation of Excel's sheet_data
|
/// Internal representation of Excel's sheet_data
|
||||||
@@ -124,7 +126,7 @@ pub struct Worksheet {
|
|||||||
pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;
|
pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.3.1.73
|
// ECMA-376-1:2016 section 18.3.1.73
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Clone)]
|
||||||
pub struct Row {
|
pub struct Row {
|
||||||
/// Row index
|
/// Row index
|
||||||
pub r: i32,
|
pub r: i32,
|
||||||
@@ -132,19 +134,23 @@ pub struct Row {
|
|||||||
pub custom_format: bool,
|
pub custom_format: bool,
|
||||||
pub custom_height: bool,
|
pub custom_height: bool,
|
||||||
pub s: i32,
|
pub s: i32,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.3.1.13
|
// ECMA-376-1:2016 section 18.3.1.13
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Clone)]
|
||||||
pub struct Col {
|
pub struct Col {
|
||||||
// Column definitions are defined on ranges, unlike rows which store unique, per-row entries.
|
// Column definitions are defined on ranges, unlike rows which store unique, per-row entries.
|
||||||
/// First column affected by this record. Settings apply to column in \[min, max\] range.
|
/// First column affected by this record. Settings apply to column in \[min, max\] range.
|
||||||
pub min: i32,
|
pub min: i32,
|
||||||
/// Last column affected by this record. Settings apply to column in \[min, max\] range.
|
/// Last column affected by this record. Settings apply to column in \[min, max\] range.
|
||||||
pub max: i32,
|
pub max: i32,
|
||||||
|
|
||||||
pub width: f64,
|
pub width: f64,
|
||||||
pub custom_width: bool,
|
pub custom_width: bool,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub style: Option<i32>,
|
pub style: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,55 +165,32 @@ pub enum CellType {
|
|||||||
CompoundData = 128,
|
CompoundData = 128,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq)]
|
||||||
|
#[serde(tag = "t", deny_unknown_fields)]
|
||||||
pub enum Cell {
|
pub enum Cell {
|
||||||
EmptyCell {
|
#[serde(rename = "empty")]
|
||||||
s: i32,
|
EmptyCell { s: i32 },
|
||||||
},
|
#[serde(rename = "b")]
|
||||||
|
BooleanCell { v: bool, s: i32 },
|
||||||
BooleanCell {
|
#[serde(rename = "n")]
|
||||||
v: bool,
|
NumberCell { v: f64, s: i32 },
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
|
|
||||||
NumberCell {
|
|
||||||
v: f64,
|
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
// Maybe we should not have this type. In Excel this is just a string
|
// Maybe we should not have this type. In Excel this is just a string
|
||||||
ErrorCell {
|
#[serde(rename = "e")]
|
||||||
ei: Error,
|
ErrorCell { ei: Error, s: i32 },
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
// Always a shared string
|
// Always a shared string
|
||||||
SharedString {
|
#[serde(rename = "s")]
|
||||||
si: i32,
|
SharedString { si: i32, s: i32 },
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
// Non evaluated Formula
|
// Non evaluated Formula
|
||||||
CellFormula {
|
#[serde(rename = "u")]
|
||||||
f: i32,
|
CellFormula { f: i32, s: i32 },
|
||||||
s: i32,
|
#[serde(rename = "fb")]
|
||||||
},
|
CellFormulaBoolean { f: i32, v: bool, s: i32 },
|
||||||
|
#[serde(rename = "fn")]
|
||||||
CellFormulaBoolean {
|
CellFormulaNumber { f: i32, v: f64, s: i32 },
|
||||||
f: i32,
|
|
||||||
v: bool,
|
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
|
|
||||||
CellFormulaNumber {
|
|
||||||
f: i32,
|
|
||||||
v: f64,
|
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
// always inline string
|
// always inline string
|
||||||
CellFormulaString {
|
#[serde(rename = "str")]
|
||||||
f: i32,
|
CellFormulaString { f: i32, v: String, s: i32 },
|
||||||
v: String,
|
#[serde(rename = "fe")]
|
||||||
s: i32,
|
|
||||||
},
|
|
||||||
|
|
||||||
CellFormulaError {
|
CellFormulaError {
|
||||||
f: i32,
|
f: i32,
|
||||||
ei: Error,
|
ei: Error,
|
||||||
@@ -226,16 +209,17 @@ impl Default for Cell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub author_name: String,
|
pub author_name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub author_id: Option<String>,
|
pub author_id: Option<String>,
|
||||||
pub cell_ref: String,
|
pub cell_ref: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECMA-376-1:2016 section 18.5.1.2
|
// ECMA-376-1:2016 section 18.5.1.2
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
@@ -243,24 +227,34 @@ pub struct Table {
|
|||||||
pub reference: String,
|
pub reference: String,
|
||||||
pub totals_row_count: u32,
|
pub totals_row_count: u32,
|
||||||
pub header_row_count: u32,
|
pub header_row_count: u32,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub header_row_dxf_id: Option<u32>,
|
pub header_row_dxf_id: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data_dxf_id: Option<u32>,
|
pub data_dxf_id: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub totals_row_dxf_id: Option<u32>,
|
pub totals_row_dxf_id: Option<u32>,
|
||||||
pub columns: Vec<TableColumn>,
|
pub columns: Vec<TableColumn>,
|
||||||
pub style_info: TableStyleInfo,
|
pub style_info: TableStyleInfo,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub has_filters: bool,
|
pub has_filters: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// totals_row_label vs totals_row_function might be mutually exclusive. Use an enum?
|
// totals_row_label vs totals_row_function might be mutually exclusive. Use an enum?
|
||||||
// the totals_row_function is an enum not String methinks
|
// the totals_row_function is an enum not String methinks
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct TableColumn {
|
pub struct TableColumn {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub totals_row_label: Option<String>,
|
pub totals_row_label: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub header_row_dxf_id: Option<u32>,
|
pub header_row_dxf_id: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data_dxf_id: Option<u32>,
|
pub data_dxf_id: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub totals_row_dxf_id: Option<u32>,
|
pub totals_row_dxf_id: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub totals_row_function: Option<String>,
|
pub totals_row_function: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,16 +272,25 @@ impl Default for TableColumn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct TableStyleInfo {
|
pub struct TableStyleInfo {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub show_first_column: bool,
|
pub show_first_column: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub show_last_column: bool,
|
pub show_last_column: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub show_row_stripes: bool,
|
pub show_row_stripes: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub show_column_stripes: bool,
|
pub show_column_stripes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Styles {
|
pub struct Styles {
|
||||||
pub num_fmts: Vec<NumFmt>,
|
pub num_fmts: Vec<NumFmt>,
|
||||||
pub fonts: Vec<Font>,
|
pub fonts: Vec<Font>,
|
||||||
@@ -312,9 +315,8 @@ impl Default for Styles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub alignment: Option<Alignment>,
|
pub alignment: Option<Alignment>,
|
||||||
pub num_fmt: String,
|
pub num_fmt: String,
|
||||||
pub fill: Fill,
|
pub fill: Fill,
|
||||||
@@ -323,7 +325,7 @@ pub struct Style {
|
|||||||
pub quote_prefix: bool,
|
pub quote_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct NumFmt {
|
pub struct NumFmt {
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub format_code: String,
|
pub format_code: String,
|
||||||
@@ -341,7 +343,7 @@ impl Default for NumFmt {
|
|||||||
// ST_FontScheme simple type (§18.18.33).
|
// ST_FontScheme simple type (§18.18.33).
|
||||||
// Usually major fonts are used for styles like headings,
|
// Usually major fonts are used for styles like headings,
|
||||||
// and minor fonts are used for body and paragraph text.
|
// and minor fonts are used for body and paragraph text.
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub enum FontScheme {
|
pub enum FontScheme {
|
||||||
@@ -361,7 +363,7 @@ impl Display for FontScheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Font {
|
pub struct Font {
|
||||||
#[serde(default = "default_as_false")]
|
#[serde(default = "default_as_false")]
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
@@ -404,7 +406,7 @@ impl Default for Font {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maybe use an enum for the pattern_type values here?
|
// TODO: Maybe use an enum for the pattern_type values here?
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Fill {
|
pub struct Fill {
|
||||||
pub pattern_type: String,
|
pub pattern_type: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -423,7 +425,7 @@ impl Default for Fill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum HorizontalAlignment {
|
pub enum HorizontalAlignment {
|
||||||
Center,
|
Center,
|
||||||
@@ -465,7 +467,7 @@ impl Display for HorizontalAlignment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum VerticalAlignment {
|
pub enum VerticalAlignment {
|
||||||
Bottom,
|
Bottom,
|
||||||
@@ -500,7 +502,7 @@ impl Display for VerticalAlignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1762
|
// 1762
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct Alignment {
|
pub struct Alignment {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(skip_serializing_if = "HorizontalAlignment::is_default")]
|
#[serde(skip_serializing_if = "HorizontalAlignment::is_default")]
|
||||||
@@ -513,17 +515,29 @@ pub struct Alignment {
|
|||||||
pub wrap_text: bool,
|
pub wrap_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CellStyleXfs {
|
pub struct CellStyleXfs {
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub font_id: i32,
|
pub font_id: i32,
|
||||||
pub fill_id: i32,
|
pub fill_id: i32,
|
||||||
pub border_id: i32,
|
pub border_id: i32,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_number_format: bool,
|
pub apply_number_format: bool,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_border: bool,
|
pub apply_border: bool,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_alignment: bool,
|
pub apply_alignment: bool,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_protection: bool,
|
pub apply_protection: bool,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_font: bool,
|
pub apply_font: bool,
|
||||||
|
#[serde(default = "default_as_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_true")]
|
||||||
pub apply_fill: bool,
|
pub apply_fill: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,24 +558,39 @@ impl Default for CellStyleXfs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct CellXfs {
|
pub struct CellXfs {
|
||||||
pub xf_id: i32,
|
pub xf_id: i32,
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
pub font_id: i32,
|
pub font_id: i32,
|
||||||
pub fill_id: i32,
|
pub fill_id: i32,
|
||||||
pub border_id: i32,
|
pub border_id: i32,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_number_format: bool,
|
pub apply_number_format: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_border: bool,
|
pub apply_border: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_alignment: bool,
|
pub apply_alignment: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_protection: bool,
|
pub apply_protection: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_font: bool,
|
pub apply_font: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub apply_fill: bool,
|
pub apply_fill: bool,
|
||||||
|
#[serde(default = "default_as_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
pub quote_prefix: bool,
|
pub quote_prefix: bool,
|
||||||
|
#[serde(skip_serializing_if = "is_default_alignment")]
|
||||||
pub alignment: Option<Alignment>,
|
pub alignment: Option<Alignment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CellStyles {
|
pub struct CellStyles {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub xf_id: i32,
|
pub xf_id: i32,
|
||||||
@@ -578,7 +607,7 @@ impl Default for CellStyles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum BorderStyle {
|
pub enum BorderStyle {
|
||||||
Thin,
|
Thin,
|
||||||
@@ -608,13 +637,13 @@ impl Display for BorderStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct BorderItem {
|
pub struct BorderItem {
|
||||||
pub style: BorderStyle,
|
pub style: BorderStyle,
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq, Eq, Clone, Default)]
|
||||||
pub struct Border {
|
pub struct Border {
|
||||||
#[serde(default = "default_as_false")]
|
#[serde(default = "default_as_false")]
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
#[serde(skip_serializing_if = "is_false")]
|
||||||
@@ -636,11 +665,10 @@ pub struct Border {
|
|||||||
|
|
||||||
/// Information need to show a sheet tab in the UI
|
/// Information need to show a sheet tab in the UI
|
||||||
/// The color is serialized only if it is not Color::None
|
/// The color is serialized only if it is not Color::None
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Decode, Encode, Debug, PartialEq)]
|
||||||
pub struct SheetProperties {
|
pub struct SheetInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
pub sheet_id: u32,
|
pub sheet_id: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,17 @@ use crate::{
|
|||||||
|
|
||||||
pub enum Units {
|
pub enum Units {
|
||||||
Number {
|
Number {
|
||||||
#[allow(dead_code)]
|
|
||||||
group_separator: bool,
|
group_separator: bool,
|
||||||
precision: i32,
|
precision: i32,
|
||||||
num_fmt: String,
|
num_fmt: String,
|
||||||
},
|
},
|
||||||
Currency {
|
Currency {
|
||||||
#[allow(dead_code)]
|
|
||||||
group_separator: bool,
|
group_separator: bool,
|
||||||
precision: i32,
|
precision: i32,
|
||||||
num_fmt: String,
|
num_fmt: String,
|
||||||
currency: String,
|
currency: String,
|
||||||
},
|
},
|
||||||
Percentage {
|
Percentage {
|
||||||
#[allow(dead_code)]
|
|
||||||
group_separator: bool,
|
group_separator: bool,
|
||||||
precision: i32,
|
precision: i32,
|
||||||
num_fmt: String,
|
num_fmt: String,
|
||||||
@@ -89,15 +86,12 @@ fn get_units_from_format_string(num_fmt: &str) -> Option<Units> {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
fn compute_cell_units(&self, cell_reference: &CellReferenceIndex) -> Option<Units> {
|
fn compute_cell_units(&self, cell_reference: &CellReferenceIndex) -> Option<Units> {
|
||||||
let cell_style_res = &self.get_style_for_cell(
|
let style = &self.get_style_for_cell(
|
||||||
cell_reference.sheet,
|
cell_reference.sheet,
|
||||||
cell_reference.row,
|
cell_reference.row,
|
||||||
cell_reference.column,
|
cell_reference.column,
|
||||||
);
|
);
|
||||||
match cell_style_res {
|
get_units_from_format_string(&style.num_fmt)
|
||||||
Ok(style) => get_units_from_format_string(&style.num_fmt),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_node_units(
|
pub(crate) fn compute_node_units(
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
|
||||||
|
|
||||||
use crate::types::{Cell, Col, Row, Style};
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
|
||||||
pub(crate) struct RowData {
|
|
||||||
pub(crate) row: Option<Row>,
|
|
||||||
pub(crate) data: HashMap<i32, Cell>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
|
||||||
pub(crate) struct ColumnData {
|
|
||||||
pub(crate) column: Option<Col>,
|
|
||||||
pub(crate) data: HashMap<i32, Cell>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
|
||||||
pub(crate) enum Diff {
|
|
||||||
// Cell diffs
|
|
||||||
SetCellValue {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
new_value: String,
|
|
||||||
old_value: Box<Option<Cell>>,
|
|
||||||
},
|
|
||||||
CellClearContents {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
old_value: Box<Option<Cell>>,
|
|
||||||
},
|
|
||||||
CellClearAll {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
old_value: Box<Option<Cell>>,
|
|
||||||
old_style: Box<Style>,
|
|
||||||
},
|
|
||||||
SetCellStyle {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
old_value: Box<Style>,
|
|
||||||
new_value: Box<Style>,
|
|
||||||
},
|
|
||||||
// Column and Row diffs
|
|
||||||
SetColumnWidth {
|
|
||||||
sheet: u32,
|
|
||||||
column: i32,
|
|
||||||
new_value: f64,
|
|
||||||
old_value: f64,
|
|
||||||
},
|
|
||||||
SetRowHeight {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
new_value: f64,
|
|
||||||
old_value: f64,
|
|
||||||
},
|
|
||||||
InsertRow {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
},
|
|
||||||
DeleteRow {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
old_data: Box<RowData>,
|
|
||||||
},
|
|
||||||
InsertColumn {
|
|
||||||
sheet: u32,
|
|
||||||
column: i32,
|
|
||||||
},
|
|
||||||
DeleteColumn {
|
|
||||||
sheet: u32,
|
|
||||||
column: i32,
|
|
||||||
old_data: Box<ColumnData>,
|
|
||||||
},
|
|
||||||
SetFrozenRowsCount {
|
|
||||||
sheet: u32,
|
|
||||||
new_value: i32,
|
|
||||||
old_value: i32,
|
|
||||||
},
|
|
||||||
SetFrozenColumnsCount {
|
|
||||||
sheet: u32,
|
|
||||||
new_value: i32,
|
|
||||||
old_value: i32,
|
|
||||||
},
|
|
||||||
DeleteSheet {
|
|
||||||
sheet: u32,
|
|
||||||
},
|
|
||||||
NewSheet {
|
|
||||||
index: u32,
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
RenameSheet {
|
|
||||||
index: u32,
|
|
||||||
old_value: String,
|
|
||||||
new_value: String,
|
|
||||||
},
|
|
||||||
SetSheetColor {
|
|
||||||
index: u32,
|
|
||||||
old_value: String,
|
|
||||||
new_value: String,
|
|
||||||
},
|
|
||||||
SetShowGridLines {
|
|
||||||
sheet: u32,
|
|
||||||
old_value: bool,
|
|
||||||
new_value: bool,
|
|
||||||
}, // FIXME: we are missing SetViewDiffs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type DiffList = Vec<Diff>;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct History {
|
|
||||||
pub(crate) undo_stack: Vec<DiffList>,
|
|
||||||
pub(crate) redo_stack: Vec<DiffList>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl History {
|
|
||||||
pub fn push(&mut self, diff_list: DiffList) {
|
|
||||||
self.undo_stack.push(diff_list);
|
|
||||||
self.redo_stack = vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn undo(&mut self) -> Option<Vec<Diff>> {
|
|
||||||
match self.undo_stack.pop() {
|
|
||||||
Some(diff_list) => {
|
|
||||||
self.redo_stack.push(diff_list.clone());
|
|
||||||
Some(diff_list)
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redo(&mut self) -> Option<Vec<Diff>> {
|
|
||||||
match self.redo_stack.pop() {
|
|
||||||
Some(diff_list) => {
|
|
||||||
self.undo_stack.push(diff_list.clone());
|
|
||||||
Some(diff_list)
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.redo_stack = vec![];
|
|
||||||
self.undo_stack = vec![];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
|
||||||
pub enum DiffType {
|
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
|
||||||
pub struct QueueDiffs {
|
|
||||||
pub r#type: DiffType,
|
|
||||||
pub list: DiffList,
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
mod history;
|
|
||||||
mod ui;
|
|
||||||
|
|
||||||
pub use common::UserModel;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub use ui::SelectedView;
|
|
||||||
|
|
||||||
pub use common::BorderArea;
|
|
||||||
pub use common::ClipboardData;
|
|
||||||
@@ -1,687 +0,0 @@
|
|||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::expressions::utils::{is_valid_column_number, is_valid_row};
|
|
||||||
|
|
||||||
use super::common::UserModel;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
|
||||||
pub struct SelectedView {
|
|
||||||
pub sheet: u32,
|
|
||||||
pub row: i32,
|
|
||||||
pub column: i32,
|
|
||||||
pub range: [i32; 4],
|
|
||||||
pub top_row: i32,
|
|
||||||
pub left_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserModel {
|
|
||||||
/// Returns the selected sheet index
|
|
||||||
pub fn get_selected_sheet(&self) -> u32 {
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the selected cell
|
|
||||||
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
return (sheet, view.row, view.column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return a safe default
|
|
||||||
(0, 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns selected view
|
|
||||||
pub fn get_selected_view(&self) -> SelectedView {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
return SelectedView {
|
|
||||||
sheet,
|
|
||||||
row: view.row,
|
|
||||||
column: view.column,
|
|
||||||
range: view.range,
|
|
||||||
top_row: view.top_row,
|
|
||||||
left_column: view.left_column,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return a safe default
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the the selected sheet
|
|
||||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&0) {
|
|
||||||
view.sheet = sheet;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the selected cell for the current view. Note that this also sets the selected range
|
|
||||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if !is_valid_column_number(column) {
|
|
||||||
return Err(format!("Invalid column: '{column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row) {
|
|
||||||
return Err(format!("Invalid row: '{row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
view.row = row;
|
|
||||||
view.column = column;
|
|
||||||
view.range = [row, column, row, column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the selected range. Note that the selected cell must be in one of the corners.
|
|
||||||
pub fn set_selected_range(
|
|
||||||
&mut self,
|
|
||||||
start_row: i32,
|
|
||||||
start_column: i32,
|
|
||||||
end_row: i32,
|
|
||||||
end_column: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_valid_column_number(start_column) {
|
|
||||||
return Err(format!("Invalid column: '{start_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(start_row) {
|
|
||||||
return Err(format!("Invalid row: '{start_row}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_column_number(end_column) {
|
|
||||||
return Err(format!("Invalid column: '{end_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(end_row) {
|
|
||||||
return Err(format!("Invalid row: '{end_row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
let selected_row = view.row;
|
|
||||||
let selected_column = view.column;
|
|
||||||
// The selected cells must be on one of the corners of the selected range:
|
|
||||||
if selected_row != start_row && selected_row != end_row {
|
|
||||||
return Err(format!(
|
|
||||||
"The selected cells is not in one of the corners. Row: '{}' and row range '({}, {})'",
|
|
||||||
selected_row, start_row, end_row
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if selected_column != start_column && selected_column != end_column {
|
|
||||||
return Err(format!(
|
|
||||||
"The selected cells is not in one of the corners. Column '{}' and column range '({}, {})'",
|
|
||||||
selected_column, start_column, end_column
|
|
||||||
));
|
|
||||||
}
|
|
||||||
view.range = [start_row, start_column, end_row, end_column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The selected range is expanded with the keyboard
|
|
||||||
pub fn on_expand_selected_range(&mut self, key: &str) -> Result<(), String> {
|
|
||||||
let (sheet, window_width, window_height) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(
|
|
||||||
view.sheet,
|
|
||||||
view.window_width as f64,
|
|
||||||
view.window_height as f64,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let (selected_row, selected_column, range, top_row, left_column) =
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
(
|
|
||||||
view.row,
|
|
||||||
view.column,
|
|
||||||
view.range,
|
|
||||||
view.top_row,
|
|
||||||
view.left_column,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let [row_start, column_start, row_end, column_end] = range;
|
|
||||||
|
|
||||||
match key {
|
|
||||||
"ArrowRight" => {
|
|
||||||
if selected_column > column_start {
|
|
||||||
let new_column = column_start + 1;
|
|
||||||
if !(is_valid_column_number(new_column)) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, new_column, row_end, column_end)?;
|
|
||||||
} else {
|
|
||||||
let new_column = column_end + 1;
|
|
||||||
if !is_valid_column_number(new_column) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// if the column is not fully visible we 'scroll' right until it is
|
|
||||||
let mut width = 0.0;
|
|
||||||
let mut c = left_column;
|
|
||||||
while c <= new_column {
|
|
||||||
width += self.model.get_column_width(sheet, c)?;
|
|
||||||
c += 1;
|
|
||||||
}
|
|
||||||
if width > window_width {
|
|
||||||
self.set_top_left_visible_cell(top_row, left_column + 1)?;
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, column_start, row_end, column_end + 1)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"ArrowLeft" => {
|
|
||||||
if selected_column < column_end {
|
|
||||||
let new_column = column_end - 1;
|
|
||||||
if !is_valid_column_number(new_column) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if new_column < left_column {
|
|
||||||
self.set_top_left_visible_cell(top_row, new_column)?;
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, column_start, row_end, new_column)?;
|
|
||||||
} else {
|
|
||||||
let new_column = column_start - 1;
|
|
||||||
if !is_valid_column_number(new_column) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if new_column < left_column {
|
|
||||||
self.set_top_left_visible_cell(top_row, new_column)?;
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, new_column, row_end, column_end)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"ArrowUp" => {
|
|
||||||
if selected_row < row_end {
|
|
||||||
let new_row = row_end - 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, column_start, new_row, column_end)?;
|
|
||||||
} else {
|
|
||||||
let new_row = row_start - 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if new_row < top_row {
|
|
||||||
self.set_top_left_visible_cell(new_row, left_column)?;
|
|
||||||
}
|
|
||||||
self.set_selected_range(new_row, column_start, row_end, column_end)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"ArrowDown" => {
|
|
||||||
if selected_row > row_start {
|
|
||||||
let new_row = row_start + 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.set_selected_range(new_row, column_start, row_end, column_end)?;
|
|
||||||
} else {
|
|
||||||
let new_row = row_end + 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let mut height = 0.0;
|
|
||||||
let mut r = top_row;
|
|
||||||
while r <= new_row + 1 {
|
|
||||||
height += self.model.get_row_height(sheet, r)?;
|
|
||||||
r += 1;
|
|
||||||
}
|
|
||||||
if height >= window_height {
|
|
||||||
self.set_top_left_visible_cell(top_row + 1, left_column)?;
|
|
||||||
}
|
|
||||||
self.set_selected_range(row_start, column_start, new_row, column_end)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the value of the first visible cell
|
|
||||||
pub fn set_top_left_visible_cell(
|
|
||||||
&mut self,
|
|
||||||
top_row: i32,
|
|
||||||
left_column: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_valid_column_number(left_column) {
|
|
||||||
return Err(format!("Invalid column: '{left_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(top_row) {
|
|
||||||
return Err(format!("Invalid row: '{top_row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
view.top_row = top_row;
|
|
||||||
view.left_column = left_column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the width of the window
|
|
||||||
pub fn set_window_width(&mut self, window_width: f64) {
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
|
|
||||||
view.window_width = window_width as i64;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the width of the window
|
|
||||||
pub fn get_window_width(&mut self) -> Result<i64, String> {
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
|
|
||||||
return Ok(view.window_width);
|
|
||||||
};
|
|
||||||
Err("View not found".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the height of the window
|
|
||||||
pub fn set_window_height(&mut self, window_height: f64) {
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
|
|
||||||
view.window_height = window_height as i64;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the height of the window
|
|
||||||
pub fn get_window_height(&mut self) -> Result<i64, String> {
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
|
|
||||||
return Ok(view.window_height);
|
|
||||||
};
|
|
||||||
Err("View not found".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User presses right arrow
|
|
||||||
pub fn on_arrow_right(&mut self) -> Result<(), String> {
|
|
||||||
let (sheet, window_width) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(view.sheet, view.window_width)
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let new_column = view.column + 1;
|
|
||||||
if !is_valid_column_number(new_column) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// if the column is not fully visible we 'scroll' right until it is
|
|
||||||
let mut width = 0.0;
|
|
||||||
let mut column = view.left_column;
|
|
||||||
while column <= new_column {
|
|
||||||
width += self.model.get_column_width(sheet, column)?;
|
|
||||||
column += 1;
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.column = new_column;
|
|
||||||
view.range = [view.row, new_column, view.row, new_column];
|
|
||||||
if width > window_width as f64 {
|
|
||||||
view.left_column += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User presses left arrow
|
|
||||||
pub fn on_arrow_left(&mut self) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let new_column = view.column - 1;
|
|
||||||
if !is_valid_column_number(new_column) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// if the column is not fully visible we 'scroll' right until it is
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.column = new_column;
|
|
||||||
view.range = [view.row, new_column, view.row, new_column];
|
|
||||||
if new_column < view.left_column {
|
|
||||||
view.left_column = new_column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User presses up arrow key
|
|
||||||
pub fn on_arrow_up(&mut self) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let new_row = view.row - 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// if the column is not fully visible we 'scroll' right until it is
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.row = new_row;
|
|
||||||
view.range = [new_row, view.column, new_row, view.column];
|
|
||||||
if new_row < view.top_row {
|
|
||||||
view.top_row = new_row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User presses down arrow key
|
|
||||||
pub fn on_arrow_down(&mut self) -> Result<(), String> {
|
|
||||||
let (sheet, window_height) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(view.sheet, view.window_height)
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let new_row = view.row + 1;
|
|
||||||
if !is_valid_row(new_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// if the row is not fully visible we 'scroll' down until it is
|
|
||||||
let mut height = 0.0;
|
|
||||||
let mut row = view.top_row;
|
|
||||||
while row <= new_row + 1 {
|
|
||||||
height += self.model.get_row_height(sheet, row)?;
|
|
||||||
row += 1;
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.row = new_row;
|
|
||||||
view.range = [new_row, view.column, new_row, view.column];
|
|
||||||
if height > window_height as f64 {
|
|
||||||
view.top_row += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This function should be memoized
|
|
||||||
/// Returns the x-coordinate of the cell in the top left corner
|
|
||||||
pub fn get_scroll_x(&self) -> Result<f64, String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let mut scroll_x = 0.0;
|
|
||||||
for column in 1..view.left_column {
|
|
||||||
scroll_x += self.model.get_column_width(sheet, column)?;
|
|
||||||
}
|
|
||||||
Ok(scroll_x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This function should be memoized
|
|
||||||
/// Returns the y-coordinate of the cell in the top left corner
|
|
||||||
pub fn get_scroll_y(&self) -> Result<f64, String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let mut scroll_y = 0.0;
|
|
||||||
for row in 1..view.top_row {
|
|
||||||
scroll_y += self.model.get_row_height(sheet, row)?;
|
|
||||||
}
|
|
||||||
Ok(scroll_y)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User presses page down.
|
|
||||||
/// The `top_row` is now the first row that is not fully visible
|
|
||||||
pub fn on_page_down(&mut self) -> Result<(), String> {
|
|
||||||
let (sheet, window_height) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(view.sheet, view.window_height)
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
let mut last_row = view.top_row;
|
|
||||||
let mut height = self.model.get_row_height(sheet, last_row)?;
|
|
||||||
while height <= window_height as f64 {
|
|
||||||
last_row += 1;
|
|
||||||
height += self.model.get_row_height(sheet, last_row)?;
|
|
||||||
}
|
|
||||||
if !is_valid_row(last_row) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let row_delta = view.row - view.top_row;
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.top_row = last_row;
|
|
||||||
view.row = view.top_row + row_delta;
|
|
||||||
view.range = [view.row, view.column, view.row, view.column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On page up. tis needs to be the inverse of page down
|
|
||||||
pub fn on_page_up(&mut self) -> Result<(), String> {
|
|
||||||
let (sheet, window_height) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(view.sheet, view.window_height as f64)
|
|
||||||
} else {
|
|
||||||
return Err("View not found".to_string());
|
|
||||||
};
|
|
||||||
let worksheet = match self.model.workbook.worksheet(sheet) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Err("Worksheet not found".to_string()),
|
|
||||||
};
|
|
||||||
let view = match worksheet.views.get(&self.model.view_id) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err("View not found".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut first_row = view.top_row;
|
|
||||||
let mut height = self.model.get_row_height(sheet, first_row)?;
|
|
||||||
while height <= window_height && first_row > 1 {
|
|
||||||
first_row -= 1;
|
|
||||||
height += self.model.get_row_height(sheet, first_row)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let row_delta = view.row - view.top_row;
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.top_row = first_row;
|
|
||||||
view.row = view.top_row + row_delta;
|
|
||||||
view.range = [view.row, view.column, view.row, view.column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We extend the selection to cell (target_row, target_column)
|
|
||||||
pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<(), String> {
|
|
||||||
let (sheet, window_width, window_height) =
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
(
|
|
||||||
view.sheet,
|
|
||||||
view.window_width as f64,
|
|
||||||
view.window_height as f64,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let (selected_row, selected_column, range, top_row, left_column) =
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
(
|
|
||||||
view.row,
|
|
||||||
view.column,
|
|
||||||
view.range,
|
|
||||||
view.top_row,
|
|
||||||
view.left_column,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let [row_start, column_start, _row_end, _column_end] = range;
|
|
||||||
|
|
||||||
let mut new_left_column = left_column;
|
|
||||||
if target_column >= selected_column {
|
|
||||||
let mut width = 0.0;
|
|
||||||
let mut column = left_column;
|
|
||||||
while column <= target_column {
|
|
||||||
width += self.model.get_column_width(sheet, column)?;
|
|
||||||
column += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while width > window_width {
|
|
||||||
width -= self.model.get_column_width(sheet, new_left_column)?;
|
|
||||||
new_left_column += 1;
|
|
||||||
}
|
|
||||||
} else if target_column < new_left_column {
|
|
||||||
new_left_column = target_column;
|
|
||||||
}
|
|
||||||
let mut new_top_row = top_row;
|
|
||||||
if target_row >= selected_row {
|
|
||||||
let mut height = 0.0;
|
|
||||||
let mut row = top_row;
|
|
||||||
while row <= target_row {
|
|
||||||
height += self.model.get_row_height(sheet, row)?;
|
|
||||||
row += 1;
|
|
||||||
}
|
|
||||||
while height > window_height {
|
|
||||||
height -= self.model.get_row_height(sheet, new_top_row)?;
|
|
||||||
new_top_row += 1;
|
|
||||||
}
|
|
||||||
} else if target_row < new_top_row {
|
|
||||||
new_top_row = target_row;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
|
|
||||||
view.range = [row_start, column_start, target_row, target_column];
|
|
||||||
if new_top_row != top_row {
|
|
||||||
view.top_row = new_top_row;
|
|
||||||
}
|
|
||||||
if new_left_column != left_column {
|
|
||||||
view.left_column = new_left_column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,4 +27,16 @@ impl Workbook {
|
|||||||
.get_mut(worksheet_index as usize)
|
.get_mut(worksheet_index as usize)
|
||||||
.ok_or_else(|| "Invalid sheet index".to_string())
|
.ok_or_else(|| "Invalid sheet index".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_worksheets_info(&self) -> Vec<SheetInfo> {
|
||||||
|
self.worksheets
|
||||||
|
.iter()
|
||||||
|
.map(|worksheet| SheetInfo {
|
||||||
|
name: worksheet.get_name(),
|
||||||
|
state: worksheet.state.to_string(),
|
||||||
|
color: worksheet.color.clone(),
|
||||||
|
sheet_id: worksheet.sheet_id,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,17 +42,7 @@ impl Worksheet {
|
|||||||
self.sheet_data.get_mut(&row)?.get_mut(&column)
|
self.sheet_data.get_mut(&row)?.get_mut(&column)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_cell(
|
fn update_cell(&mut self, row: i32, column: i32, new_cell: Cell) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
new_cell: Cell,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// validate row and column arg before updating cell of worksheet
|
|
||||||
if !is_valid_row(row) || !is_valid_column_number(column) {
|
|
||||||
return Err("Incorrect row or column".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.sheet_data.get_mut(&row) {
|
match self.sheet_data.get_mut(&row) {
|
||||||
Some(column_data) => match column_data.get(&column) {
|
Some(column_data) => match column_data.get(&column) {
|
||||||
Some(_cell) => {
|
Some(_cell) => {
|
||||||
@@ -68,7 +58,6 @@ impl Worksheet {
|
|||||||
self.sheet_data.insert(row, column_data);
|
self.sheet_data.insert(row, column_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [MVP]: Pass the cell style from the model
|
// TODO [MVP]: Pass the cell style from the model
|
||||||
@@ -79,8 +68,9 @@ impl Worksheet {
|
|||||||
if row.r == row_index {
|
if row.r == row_index {
|
||||||
if row.custom_format {
|
if row.custom_format {
|
||||||
return row.s;
|
return row.s;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cols = &self.cols;
|
let cols = &self.cols;
|
||||||
@@ -116,8 +106,64 @@ impl Worksheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
|
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 cols = &mut self.cols;
|
||||||
self.set_column_width_and_style(column, width, Some(style_index))
|
let col = Col {
|
||||||
|
min: column,
|
||||||
|
max: column,
|
||||||
|
width: constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR,
|
||||||
|
custom_width: true,
|
||||||
|
style: Some(style_index),
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
if min == column && max == column {
|
||||||
|
c.style = Some(style_index);
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
// We need to split the result
|
||||||
|
split = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if column < min {
|
||||||
|
// We passed, we should insert at index
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
if split {
|
||||||
|
let min = cols[index].min;
|
||||||
|
let max = cols[index].max;
|
||||||
|
let pre = Col {
|
||||||
|
min,
|
||||||
|
max: column - 1,
|
||||||
|
width: cols[index].width,
|
||||||
|
custom_width: cols[index].custom_width,
|
||||||
|
style: cols[index].style,
|
||||||
|
};
|
||||||
|
let post = Col {
|
||||||
|
min: column + 1,
|
||||||
|
max,
|
||||||
|
width: cols[index].width,
|
||||||
|
custom_width: cols[index].custom_width,
|
||||||
|
style: cols[index].style,
|
||||||
|
};
|
||||||
|
cols.remove(index);
|
||||||
|
if column != max {
|
||||||
|
cols.insert(index, post);
|
||||||
|
}
|
||||||
|
cols.insert(index, col);
|
||||||
|
if column != min {
|
||||||
|
cols.insert(index, pre);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cols.insert(index, col);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
|
pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
|
||||||
@@ -139,101 +185,59 @@ impl Worksheet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_style(
|
pub fn set_cell_style(&mut self, row: i32, column: i32, style_index: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
style_index: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
match self.cell_mut(row, column) {
|
match self.cell_mut(row, column) {
|
||||||
Some(cell) => {
|
Some(cell) => {
|
||||||
cell.set_style(style_index);
|
cell.set_style(style_index);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.cell_clear_contents_with_style(row, column, style_index)?;
|
self.set_cell_empty_with_style(row, column, style_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
|
||||||
// TODO: cleanup check if the old cell style is still in use
|
// TODO: cleanup check if the old cell style is still in use
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_with_formula(
|
pub fn set_cell_with_formula(&mut self, row: i32, column: i32, index: i32, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
index: i32,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::new_formula(index, style);
|
let cell = Cell::new_formula(index, style);
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_with_number(
|
pub fn set_cell_with_number(&mut self, row: i32, column: i32, value: f64, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: f64,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::new_number(value, style);
|
let cell = Cell::new_number(value, style);
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_with_string(
|
pub fn set_cell_with_string(&mut self, row: i32, column: i32, index: i32, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
index: i32,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::new_string(index, style);
|
let cell = Cell::new_string(index, style);
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_with_boolean(
|
pub fn set_cell_with_boolean(&mut self, row: i32, column: i32, value: bool, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
value: bool,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::new_boolean(value, style);
|
let cell = Cell::new_boolean(value, style);
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cell_with_error(
|
pub fn set_cell_with_error(&mut self, row: i32, column: i32, error: Error, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
error: Error,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::new_error(error, style);
|
let cell = Cell::new_error(error, style);
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cell_clear_contents(&mut self, row: i32, column: i32) -> Result<(), String> {
|
pub fn set_cell_empty(&mut self, row: i32, column: i32) {
|
||||||
let s = self.get_style(row, column);
|
let s = self.get_style(row, column);
|
||||||
let cell = Cell::EmptyCell { s };
|
let cell = Cell::EmptyCell { s };
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cell_clear_contents_with_style(
|
pub fn set_cell_empty_with_style(&mut self, row: i32, column: i32, style: i32) {
|
||||||
&mut self,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
style: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let cell = Cell::EmptyCell { s: style };
|
let cell = Cell::EmptyCell { s: style };
|
||||||
self.update_cell(row, column, cell)
|
self.update_cell(row, column, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_frozen_rows(&mut self, frozen_rows: i32) -> Result<(), String> {
|
pub fn set_frozen_rows(&mut self, frozen_rows: i32) -> Result<(), String> {
|
||||||
if frozen_rows < 0 {
|
if frozen_rows < 0 {
|
||||||
return Err("Frozen rows cannot be negative".to_string());
|
return Err("Frozen rows cannot be negative".to_string());
|
||||||
}
|
} else if frozen_rows >= constants::LAST_ROW {
|
||||||
if frozen_rows >= constants::LAST_ROW {
|
|
||||||
return Err("Too many rows".to_string());
|
return Err("Too many rows".to_string());
|
||||||
}
|
}
|
||||||
self.frozen_rows = frozen_rows;
|
self.frozen_rows = frozen_rows;
|
||||||
@@ -243,8 +247,7 @@ impl Worksheet {
|
|||||||
pub fn set_frozen_columns(&mut self, frozen_columns: i32) -> Result<(), String> {
|
pub fn set_frozen_columns(&mut self, frozen_columns: i32) -> Result<(), String> {
|
||||||
if frozen_columns < 0 {
|
if frozen_columns < 0 {
|
||||||
return Err("Frozen columns cannot be negative".to_string());
|
return Err("Frozen columns cannot be negative".to_string());
|
||||||
}
|
} else if frozen_columns >= constants::LAST_COLUMN {
|
||||||
if frozen_columns >= constants::LAST_COLUMN {
|
|
||||||
return Err("Too many columns".to_string());
|
return Err("Too many columns".to_string());
|
||||||
}
|
}
|
||||||
self.frozen_columns = frozen_columns;
|
self.frozen_columns = frozen_columns;
|
||||||
@@ -278,21 +281,11 @@ impl Worksheet {
|
|||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the width of a column.
|
/// Changes the width of a column.
|
||||||
/// * If the column does not a have a width we simply add it
|
/// * 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 ned to split the range.
|
||||||
/// Fails if column index is outside allowed range.
|
/// Fails if column index is outside allowed range.
|
||||||
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
|
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
|
||||||
self.set_column_width_and_style(column, width, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_column_width_and_style(
|
|
||||||
&mut self,
|
|
||||||
column: i32,
|
|
||||||
width: f64,
|
|
||||||
style: Option<i32>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
if !is_valid_column_number(column) {
|
if !is_valid_column_number(column) {
|
||||||
return Err(format!("Column number '{column}' is not valid."));
|
return Err(format!("Column number '{column}' is not valid."));
|
||||||
}
|
}
|
||||||
@@ -302,7 +295,7 @@ impl Worksheet {
|
|||||||
max: column,
|
max: column,
|
||||||
width: width / constants::COLUMN_WIDTH_FACTOR,
|
width: width / constants::COLUMN_WIDTH_FACTOR,
|
||||||
custom_width: true,
|
custom_width: true,
|
||||||
style,
|
style: None,
|
||||||
};
|
};
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
let mut split = false;
|
let mut split = false;
|
||||||
@@ -313,9 +306,11 @@ impl Worksheet {
|
|||||||
if min == column && max == column {
|
if min == column && max == column {
|
||||||
c.width = width / constants::COLUMN_WIDTH_FACTOR;
|
c.width = width / constants::COLUMN_WIDTH_FACTOR;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
// We need to split the result
|
||||||
|
split = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
split = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if column < min {
|
if column < min {
|
||||||
// We passed, we should insert at index
|
// We passed, we should insert at index
|
||||||
@@ -356,7 +351,7 @@ impl Worksheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the width of a column in pixels
|
/// Return the width of a column in pixels
|
||||||
pub fn get_column_width(&self, column: i32) -> Result<f64, String> {
|
pub fn column_width(&self, column: i32) -> Result<f64, String> {
|
||||||
if !is_valid_column_number(column) {
|
if !is_valid_column_number(column) {
|
||||||
return Err(format!("Column number '{column}' is not valid."));
|
return Err(format!("Column number '{column}' is not valid."));
|
||||||
}
|
}
|
||||||
@@ -368,8 +363,9 @@ impl Worksheet {
|
|||||||
if column >= min && column <= max {
|
if column >= min && column <= max {
|
||||||
if col.custom_width {
|
if col.custom_width {
|
||||||
return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
|
return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(constants::DEFAULT_COLUMN_WIDTH)
|
Ok(constants::DEFAULT_COLUMN_WIDTH)
|
||||||
|
|||||||