Compare commits
135 Commits
feature/ni
...
varum-chan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7858f7aa9 | ||
|
|
3270d587ac | ||
|
|
e065477b5a | ||
|
|
1f5f575e7a | ||
|
|
472740f296 | ||
|
|
430b420435 | ||
|
|
48fd51fc8a | ||
|
|
614d71b61c | ||
|
|
38e21b9639 | ||
|
|
d6a462dbe3 | ||
|
|
c1df2cec0b | ||
|
|
283a44e109 | ||
|
|
f7dac8b015 | ||
|
|
df0aa51d14 | ||
|
|
411c5de59b | ||
|
|
0df132d5c2 | ||
|
|
8cdb3b8c60 | ||
|
|
d08fe32f97 | ||
|
|
47acd0d600 | ||
|
|
d70ab85396 | ||
|
|
65b959cb1c | ||
|
|
726fc1399d | ||
|
|
8ed0ab25f6 | ||
|
|
949eafc97f | ||
|
|
1f1fd24334 | ||
|
|
2b0c24de55 | ||
|
|
1aa9b6a220 | ||
|
|
94ebf33656 | ||
|
|
b3d4c479f6 | ||
|
|
67aaa85a9a | ||
|
|
5d2953b894 | ||
|
|
b84aeb8bb9 | ||
|
|
c5576af81b | ||
|
|
cb38eff899 | ||
|
|
16212b1518 | ||
|
|
8243e231ab | ||
|
|
48afb45eb9 | ||
|
|
97846041e5 | ||
|
|
c1be1e47cb | ||
|
|
7efdbede3c | ||
|
|
be6819fec3 | ||
|
|
e130f784fd | ||
|
|
c2a2983937 | ||
|
|
3c49f9a606 | ||
|
|
63817e2d50 | ||
|
|
028ae1ce98 | ||
|
|
a4a40c6fd0 | ||
|
|
0ed2984358 | ||
|
|
0b1199056f | ||
|
|
726bf677ed | ||
|
|
d681f63b25 | ||
|
|
ba5869420b | ||
|
|
420ea9829c | ||
|
|
49ae2d8915 | ||
|
|
bdd2c8fe04 | ||
|
|
24dd63b261 | ||
|
|
861700cb45 | ||
|
|
98dc557a01 | ||
|
|
2c2228c2c2 | ||
|
|
494a315cbd | ||
|
|
0c69889832 | ||
|
|
04d8c658ab | ||
|
|
dad4755b16 | ||
|
|
75d8a5282e | ||
|
|
f78027247b | ||
|
|
ee6a41c4f4 | ||
|
|
b7336f70d6 | ||
|
|
dae37f14ba | ||
|
|
7ffbfac432 | ||
|
|
f9ea4fd757 | ||
|
|
7446932519 | ||
|
|
d55845e69f | ||
|
|
9e5b959ccc | ||
|
|
ffa93309e2 | ||
|
|
79216b286b | ||
|
|
411d4a3780 | ||
|
|
3a7aa15347 | ||
|
|
090e852054 | ||
|
|
3e54ad5b3c | ||
|
|
7b12c2682e | ||
|
|
80273a88ec | ||
|
|
3d951c5c50 | ||
|
|
cd54389e91 | ||
|
|
843d8beb02 | ||
|
|
09ac29785d | ||
|
|
2b530423c8 | ||
|
|
51c41900d7 | ||
|
|
730a815729 | ||
|
|
9805d0c518 | ||
|
|
10a9d36f3d | ||
|
|
480640dc98 | ||
|
|
3058a63e4f | ||
|
|
8275d73b64 | ||
|
|
072abb2240 | ||
|
|
9a46e5ccc7 | ||
|
|
585e594d8d | ||
|
|
248ef66e7c | ||
|
|
15da2e5785 | ||
|
|
39174add1f | ||
|
|
e412f5fc22 | ||
|
|
42c1a39131 | ||
|
|
f26cdd3a4b | ||
|
|
4016eb5944 | ||
|
|
58dfdd329e | ||
|
|
4a290aec7c | ||
|
|
3966dbc790 | ||
|
|
abd4ce4ea5 | ||
|
|
02da1eb388 | ||
|
|
1131234531 | ||
|
|
b495397b5f | ||
|
|
8c0a566995 | ||
|
|
dd62dd2dc6 | ||
|
|
79b7b9b817 | ||
|
|
06ae1a1d6d | ||
|
|
6390739fd4 | ||
|
|
e41741cf77 | ||
|
|
48719b6416 | ||
|
|
53d3d5144c | ||
|
|
bf9a1ed9f4 | ||
|
|
11df4a55c7 | ||
|
|
00b5b65588 | ||
|
|
2b03b3e3b9 | ||
|
|
83a4431417 | ||
|
|
40aa8bebaf | ||
|
|
b9bf485379 | ||
|
|
dc23a7f29c | ||
|
|
083548608e | ||
|
|
0ba80035d2 | ||
|
|
55a043366a | ||
|
|
864a37f1e6 | ||
|
|
72c7c94f3d | ||
|
|
c3a9b006d2 | ||
|
|
b37397acb8 | ||
|
|
49c3b14bf0 | ||
|
|
d2cba48f8e |
18
.github/workflows/publish-wiki.yml
vendored
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
|
||||
8
.github/workflows/test-coverage.yaml
vendored
8
.github/workflows/test-coverage.yaml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Coverage
|
||||
|
||||
on: [pull_request, push]
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
@@ -14,7 +18,7 @@ jobs:
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
|
||||
run: cargo llvm-cov --all-features --workspace --exclude pyroncalc --exclude wasm --lcov --output-path lcov.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
|
||||
32
.readthedocs.yaml
Normal file
32
.readthedocs.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
# .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
|
||||
26
CHANGELOG.md
Normal file
26
CHANGELOG.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# CHANGELOG
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- New function UNICODE ([#128](https://github.com/ironcalc/IronCalc/pull/128))
|
||||
- New document server (Thanks Dani!)
|
||||
- New function FORMULATEXT
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed several issues with pasting content
|
||||
- Fixed several issues with borders
|
||||
|
||||
## [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
|
||||
73
CONTRIBUTING.md
Normal file
73
CONTRIBUTING.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Contributing to IronCalc
|
||||
|
||||
Whether you are a seasoned developer or a rookie, welcome to IronCalc!
|
||||
|
||||
🎉 We appreciate your interest in contributing to our project.
|
||||
|
||||
Before starting any work it is best if you get in touch to make sure your work is relevant.
|
||||
|
||||
Please be patient, I am only one and this is a side project.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Changes to the main repo
|
||||
|
||||
If are comfortable working with GitHub and Git, the following steps should be straightforward. For more general information visit [GitHub Docs](https://docs.github.com/en) and [Git Documentation](https://git-scm.com/doc).
|
||||
|
||||
1. **Fork the repository**
|
||||
Start by forking the repository to your own GitHub account. You can do this by clicking the "Fork" button on the top right of the repository page.
|
||||
|
||||
2. **Clone the original repository**
|
||||
Clone the original repository to your local machine:
|
||||
```bash
|
||||
git clone https://github.com/ironcalc/IronCalc.git
|
||||
cd IronCalc
|
||||
```
|
||||
|
||||
|
||||
3. **Add your fork as a remote**
|
||||
Add your forked repository as a remote named fork:
|
||||
|
||||
```bash
|
||||
git remote add fork https://github.com/<your-username>/IronCalc.git
|
||||
```
|
||||
4. **Create a new branch**
|
||||
Always create a new branch for your changes to keep your work isolated:
|
||||
|
||||
```bash
|
||||
git checkout -b your-feature-name
|
||||
```
|
||||
5. **Make changes**
|
||||
Implement your changes, improvements, or bug fixes. Make sure to follow any coding style or project-specific guidelines.
|
||||
|
||||
6. **Commit your changes**
|
||||
Write clear and concise commit messages:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Brief description of your changes"
|
||||
```
|
||||
7. **Push to your fork**
|
||||
Push your branch to your forked repository:
|
||||
```bash
|
||||
git push fork your-feature-name
|
||||
````
|
||||
|
||||
8. **Create a Pull Request (PR)**
|
||||
Follow the steps on the terminal or go to the orig IronCalc repository, and click on "New Pull Request."
|
||||
Ensure your PR has a clear title and description explaining the purpose of your changes.
|
||||
|
||||
Always start from the main branch in a clean state. `git pull` will generally get the lastest changes form the original repo.
|
||||
|
||||
You should make sure that your changes are properly tested.
|
||||
|
||||
# 🤝 Community and Support
|
||||
|
||||
Feel free to reach out if you have questions or need help. Via GitHub, email, our discord server or bluesky.
|
||||
|
||||
* Open an issue to report a bug or discuss a feature before implementing it.
|
||||
* Engage with the community to share ideas or seek guidance.
|
||||
|
||||
Note that not all contributors need to be coding. To contribute testing, bug reports, typos, ideas of all kinds, you can just send us an email.
|
||||
|
||||
Thank you for your contributions! 💪 Together, we can make IronCalc even better.
|
||||
195
Cargo.lock
generated
195
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
@@ -43,6 +43,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
@@ -57,19 +63,22 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitcode"
|
||||
version = "0.6.0"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48bc1c27654127a24c476d40198746860ef56475f41a601bfa5c4d0f832968f0"
|
||||
checksum = "ee1bce7608560cd4bf0296a4262d0dbf13e6bcec5ff2105724c8ab88cc7fc784"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitcode_derive",
|
||||
"bytemuck",
|
||||
"glam",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcode_derive"
|
||||
version = "0.6.0"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2966755a19aad59ee2aae91e2d48842c667a99d818ec72168efdab07200701cc"
|
||||
checksum = "a539389a13af092cd345a2b47ae7dec12deb306d660b2223d25cd3419b253ebe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -142,9 +151,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -156,9 +165,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
@@ -167,12 +176,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||
checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
@@ -242,6 +250,27 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@@ -299,6 +328,18 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
@@ -331,6 +372,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
@@ -342,7 +389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ironcalc"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bitcode",
|
||||
"chrono",
|
||||
@@ -358,15 +405,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ironcalc_base"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bitcode",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"csv",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"regex",
|
||||
"regex-lite",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -423,6 +472,15 @@ version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
@@ -455,9 +513,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
@@ -529,6 +587,12 @@ version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -543,13 +607,86 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyroncalc"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"ironcalc",
|
||||
"pyo3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
@@ -612,6 +749,12 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
@@ -714,15 +857,21 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
@@ -774,6 +923,12 @@ version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
"base",
|
||||
"xlsx",
|
||||
"bindings/wasm",
|
||||
"bindings/python",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
|
||||
29
Makefile
29
Makefile
@@ -1,41 +1,44 @@
|
||||
.PHONY: lint
|
||||
lint:
|
||||
cargo fmt -- --check
|
||||
cargo clippy --all-targets --all-features
|
||||
cargo clippy --all-targets --all-features -- -W clippy::unwrap_used -W clippy::expect_used -W clippy::panic -D warnings
|
||||
cd webapp && npm install && npm run check
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
cargo fmt
|
||||
|
||||
.PHONY: tests
|
||||
tests: lint
|
||||
cargo test
|
||||
./target/debug/documentation
|
||||
cmp functions.md wiki/functions.md || exit 1
|
||||
make remove-artifacts
|
||||
cd bindings/wasm/ && wasm-pack build --target nodejs && node tests/test.mjs
|
||||
# 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-artifacts:
|
||||
rm -f xlsx/hello-calc.xlsx
|
||||
rm -f xlsx/hello-styles.xlsx
|
||||
rm -f xlsx/widths-and-heights.xlsx
|
||||
rm -f functions.md
|
||||
|
||||
.PHONY: clean
|
||||
clean: remove-artifacts
|
||||
cargo clean
|
||||
rm -r -f base/target
|
||||
rm -r -f xlsx/target
|
||||
rm -r -f bindings/python/target
|
||||
rm -r -f bindings/wasm/targets
|
||||
rm -f cargo-test-*
|
||||
rm -f base/cargo-test-*
|
||||
rm -f xlsx/cargo-test-*
|
||||
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
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
|
||||
|
||||
update-docs:
|
||||
cargo build
|
||||
./target/debug/documentation -o wiki/functions.md
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
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:
|
||||
|
||||
https://playground.ironcalc.com
|
||||
https://app.ironcalc.com
|
||||
|
||||
|
||||
# Collaborators needed!. Call to action
|
||||
|
||||
We don't have a vibrant community just yet. This is the very stages of the project. But if you are passionate about code with high standards and no compromises, if you are looking for a project with high impact, if you are interested in a better, more open infrastructure for spreadsheets, whether you are a developer (rust, python, TypeScript, electron/tauri/anything else native app, React, you name it), a designer (we need a logo desperately!), an Excel power user who wants features, a business looking to integrate a MIT/Apache licensed spreadsheet in your own SaaS application join us!
|
||||
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!
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ironcalc_base"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
||||
edition = "2021"
|
||||
homepage = "https://www.ironcalc.com"
|
||||
@@ -14,10 +14,17 @@ readme = "README.md"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
ryu = "1.0"
|
||||
chrono = "0.4"
|
||||
chrono-tz = "0.9"
|
||||
regex = "1.0"
|
||||
chrono-tz = "0.10"
|
||||
regex = { version = "1.0", optional = true}
|
||||
regex-lite = { version = "0.1.6", optional = true}
|
||||
once_cell = "1.16.0"
|
||||
bitcode = "0.6.0"
|
||||
bitcode = "0.6.3"
|
||||
csv = "1.3.0"
|
||||
|
||||
[features]
|
||||
default = ["use_regex_full"]
|
||||
use_regex_full = ["regex"]
|
||||
use_regex_lite = ["regex-lite"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -3,15 +3,15 @@ use ironcalc_base::{types::CellType, Model};
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
||||
// A1
|
||||
model.set_user_input(0, 1, 1, "1".to_string());
|
||||
model.set_user_input(0, 1, 1, "1".to_string())?;
|
||||
// A2
|
||||
model.set_user_input(0, 2, 1, "2".to_string());
|
||||
model.set_user_input(0, 2, 1, "2".to_string())?;
|
||||
// A3
|
||||
model.set_user_input(0, 3, 1, "3".to_string());
|
||||
model.set_user_input(0, 3, 1, "3".to_string())?;
|
||||
// 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
|
||||
model.set_user_input(0, 2, 2, "=B1/0".to_string());
|
||||
model.set_user_input(0, 2, 2, "=B1/0".to_string())?;
|
||||
// Evaluate
|
||||
model.evaluate();
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ use ironcalc_base::{cell::CellValue, Model};
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
||||
// A1
|
||||
model.set_user_input(0, 1, 1, "Hello".to_string());
|
||||
model.set_user_input(0, 1, 1, "Hello".to_string())?;
|
||||
// B1
|
||||
model.set_user_input(0, 1, 2, "world!".to_string());
|
||||
model.set_user_input(0, 1, 2, "world!".to_string())?;
|
||||
// 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
|
||||
model.evaluate();
|
||||
|
||||
|
||||
@@ -79,12 +79,11 @@ impl Model {
|
||||
let formula_or_value = self
|
||||
.get_cell_formula(sheet, source_row, source_column)?
|
||||
.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
|
||||
.worksheet_mut(sheet)?
|
||||
.set_cell_style(target_row, target_column, style);
|
||||
self.cell_clear_all(sheet, source_row, source_column)?;
|
||||
Ok(())
|
||||
.set_cell_style(target_row, target_column, style)?;
|
||||
self.cell_clear_all(sheet, source_row, source_column)
|
||||
}
|
||||
|
||||
/// Inserts one or more new columns into the model at the specified index.
|
||||
@@ -384,11 +383,11 @@ impl Model {
|
||||
/// * All cell references to initial_column will go to target_column
|
||||
/// * All cell references to columns in between (initial_column, target_column] will be displaced one to the left
|
||||
/// * All other cell references are left unchanged
|
||||
/// Ranges. This is the tricky bit:
|
||||
/// Ranges. This is the tricky bit:
|
||||
/// * Column is one of the extremes of the range. The new extreme would be target_column.
|
||||
/// Range is then normalized
|
||||
/// * Any other case, range is left unchanged.
|
||||
/// NOTE: This does NOT move the data in the columns or move the colum styles
|
||||
/// NOTE: This does NOT move the data in the columns or move the colum styles
|
||||
pub fn move_column_action(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
|
||||
@@ -176,3 +176,18 @@ impl Cell {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing methods for MergedCells struct
|
||||
|
||||
impl MergedCells {
|
||||
pub fn is_cell_part_of_merged_cells(&self, row: i32, col: i32) -> bool {
|
||||
// This is merge Mother cell so do not include this cell as part of Merged Cells
|
||||
if row == self.0 && col == self.1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let result: bool = (row >= self.0 && row <= self.2) && (col >= self.1 && col <= self.3);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/// COLUMN_WIDTH and ROW_HEIGHT are pixel values
|
||||
/// A column width of Excel value `w` will result in `w * COLUMN_WIDTH_FACTOR` pixels
|
||||
/// Note that these constants are inlined
|
||||
pub(crate) const DEFAULT_COLUMN_WIDTH: f64 = 100.0;
|
||||
pub(crate) const DEFAULT_ROW_HEIGHT: f64 = 21.0;
|
||||
pub(crate) const DEFAULT_COLUMN_WIDTH: f64 = 125.0;
|
||||
pub(crate) const DEFAULT_ROW_HEIGHT: f64 = 28.0;
|
||||
pub(crate) const COLUMN_WIDTH_FACTOR: f64 = 12.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_ROW: i32 = 1_048_576;
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct SetCellValue {
|
||||
}
|
||||
|
||||
impl Model {
|
||||
#[allow(clippy::expect_used)]
|
||||
pub(crate) fn shift_cell_formula(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
@@ -57,6 +58,7 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
pub fn forward_references(
|
||||
&mut self,
|
||||
source_area: &Area,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use crate::expressions::{
|
||||
lexer::util::get_tokens,
|
||||
token::{OpCompare, OpSum, TokenType},
|
||||
|
||||
@@ -52,7 +52,9 @@ pub fn get_tokens(formula: &str) -> Vec<MarkedToken> {
|
||||
let mut lexer = Lexer::new(
|
||||
formula,
|
||||
LexerMode::A1,
|
||||
#[allow(clippy::expect_used)]
|
||||
get_locale("en").expect(""),
|
||||
#[allow(clippy::expect_used)]
|
||||
get_language("en").expect(""),
|
||||
);
|
||||
let mut start = lexer.get_position();
|
||||
|
||||
@@ -63,7 +63,9 @@ pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String>
|
||||
let mut lexer = lexer::Lexer::new(
|
||||
formula,
|
||||
lexer::LexerMode::A1,
|
||||
#[allow(clippy::expect_used)]
|
||||
get_locale("en").expect(""),
|
||||
#[allow(clippy::expect_used)]
|
||||
get_language("en").expect(""),
|
||||
);
|
||||
if let TokenType::Range {
|
||||
@@ -202,7 +204,9 @@ impl Parser {
|
||||
let lexer = lexer::Lexer::new(
|
||||
"",
|
||||
lexer::LexerMode::A1,
|
||||
#[allow(clippy::expect_used)]
|
||||
get_locale("en").expect(""),
|
||||
#[allow(clippy::expect_used)]
|
||||
get_language("en").expect(""),
|
||||
);
|
||||
Parser {
|
||||
@@ -675,14 +679,23 @@ impl Parser {
|
||||
}
|
||||
};
|
||||
// table-name => table
|
||||
let table = self.tables.get(&table_name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Table not found: '{table_name}' at '{}!{}{}'",
|
||||
context.sheet,
|
||||
number_to_column(context.column).expect(""),
|
||||
context.row
|
||||
)
|
||||
});
|
||||
let table = match self.tables.get(&table_name) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
let message = format!(
|
||||
"Table not found: '{table_name}' at '{}!{}{}'",
|
||||
context.sheet,
|
||||
number_to_column(context.column)
|
||||
.unwrap_or(format!("{}", context.column)),
|
||||
context.row
|
||||
);
|
||||
return Node::ParseErrorKind {
|
||||
formula: self.lexer.get_formula(),
|
||||
position: 0,
|
||||
message,
|
||||
};
|
||||
}
|
||||
};
|
||||
let table_sheet_index = match self.get_sheet_index_by_name(&table.sheet_name) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
@@ -701,6 +714,7 @@ impl Parser {
|
||||
};
|
||||
|
||||
// context must be with tables.reference
|
||||
#[allow(clippy::expect_used)]
|
||||
let (column_start, mut row_start, column_end, mut row_end) =
|
||||
parse_range(&table.reference).expect("Failed parsing range");
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::panic)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::expressions::lexer::LexerMode;
|
||||
|
||||
@@ -79,7 +79,7 @@ impl fmt::Display for OpProduct {
|
||||
/// Note that "#ERROR!" and "#N/IMPL!" are not part of the xlsx standard
|
||||
/// * "#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
|
||||
/// 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)]
|
||||
pub enum Error {
|
||||
REF,
|
||||
|
||||
@@ -5,6 +5,7 @@ use chrono::NaiveDate;
|
||||
use crate::constants::EXCEL_DATE_BASE;
|
||||
|
||||
pub fn from_excel_date(days: i64) -> NaiveDate {
|
||||
#[allow(clippy::expect_used)]
|
||||
let dt = NaiveDate::from_ymd_opt(1900, 1, 1).expect("problem with chrono::NaiveDate");
|
||||
dt + Duration::days(days - 2)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const EPS_LOW: f64 = 1e-6;
|
||||
|
||||
// Known values computed with Arb via Nemo.jl in Julia
|
||||
// You can also use Mathematica
|
||||
/// But please do not use Excel or any other software without arbitrary precision
|
||||
// But please do not use Excel or any other software without arbitrary precision
|
||||
|
||||
fn numbers_are_close(a: f64, b: f64) -> bool {
|
||||
if a == b {
|
||||
|
||||
@@ -194,16 +194,24 @@ fn compute_ppmt(
|
||||
// In these formulas the payment (pmt) is normally negative
|
||||
|
||||
impl Model {
|
||||
// FIXME: These three functions (get_array_of_numbers..) need to be refactored
|
||||
// They are really similar expect for small issues
|
||||
fn get_array_of_numbers(
|
||||
fn get_array_of_numbers_generic(
|
||||
&mut self,
|
||||
arg: &Node,
|
||||
cell: &CellReferenceIndex,
|
||||
accept_number_node: bool,
|
||||
handle_empty_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
|
||||
handle_non_number_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
|
||||
) -> Result<Vec<f64>, CalcResult> {
|
||||
let mut values = Vec::new();
|
||||
match self.evaluate_node_in_context(arg, *cell) {
|
||||
CalcResult::Number(value) => values.push(value),
|
||||
CalcResult::Number(value) if accept_number_node => values.push(value),
|
||||
CalcResult::Number(_) => {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
*cell,
|
||||
"Expected range of numbers".to_string(),
|
||||
));
|
||||
}
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return Err(CalcResult::new_error(
|
||||
@@ -212,6 +220,7 @@ impl Model {
|
||||
"Ranges are in different sheets".to_string(),
|
||||
));
|
||||
}
|
||||
let sheet = left.sheet;
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
@@ -219,32 +228,46 @@ impl Model {
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.worksheet(sheet)
|
||||
.map_err(|_| {
|
||||
CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
*cell,
|
||||
format!("Invalid worksheet index: '{}'", sheet),
|
||||
)
|
||||
})?
|
||||
.dimension()
|
||||
.max_row;
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.worksheet(sheet)
|
||||
.map_err(|_| {
|
||||
CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
*cell,
|
||||
format!("Invalid worksheet index: '{}'", sheet),
|
||||
)
|
||||
})?
|
||||
.dimension()
|
||||
.max_column;
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
values.push(value);
|
||||
}
|
||||
for row in row1..=row2 {
|
||||
for column in column1..=column2 {
|
||||
let cell_ref = CellReferenceIndex { sheet, row, column };
|
||||
match self.evaluate_cell(cell_ref) {
|
||||
CalcResult::Number(value) => values.push(value),
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
CalcResult::EmptyCell => {
|
||||
if let Some(value) = handle_empty_cell()? {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// We ignore booleans and strings
|
||||
if let Some(value) = handle_non_number_cell()? {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,88 +275,51 @@ impl Model {
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
_ => {
|
||||
// We ignore booleans and strings
|
||||
handle_non_number_cell()?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
fn get_array_of_numbers(
|
||||
&mut self,
|
||||
arg: &Node,
|
||||
cell: &CellReferenceIndex,
|
||||
) -> Result<Vec<f64>, CalcResult> {
|
||||
self.get_array_of_numbers_generic(
|
||||
arg,
|
||||
cell,
|
||||
true, // accept_number_node
|
||||
|| Ok(None), // Ignore empty cells
|
||||
|| Ok(None), // Ignore non-number cells
|
||||
)
|
||||
}
|
||||
|
||||
fn get_array_of_numbers_xpnv(
|
||||
&mut self,
|
||||
arg: &Node,
|
||||
cell: &CellReferenceIndex,
|
||||
error: Error,
|
||||
) -> Result<Vec<f64>, CalcResult> {
|
||||
let mut values = Vec::new();
|
||||
match self.evaluate_node_in_context(arg, *cell) {
|
||||
CalcResult::Number(value) => values.push(value),
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
*cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
));
|
||||
}
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
values.push(value);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
CalcResult::EmptyCell => {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::NUM,
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(CalcResult::new_error(
|
||||
error,
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
_ => {
|
||||
return Err(CalcResult::new_error(
|
||||
error,
|
||||
self.get_array_of_numbers_generic(
|
||||
arg,
|
||||
cell,
|
||||
true, // accept_number_node
|
||||
|| {
|
||||
Err(CalcResult::new_error(
|
||||
Error::NUM,
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(values)
|
||||
))
|
||||
},
|
||||
|| {
|
||||
Err(CalcResult::new_error(
|
||||
error.clone(),
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_array_of_numbers_xirr(
|
||||
@@ -341,69 +327,19 @@ impl Model {
|
||||
arg: &Node,
|
||||
cell: &CellReferenceIndex,
|
||||
) -> Result<Vec<f64>, CalcResult> {
|
||||
let mut values = Vec::new();
|
||||
match self.evaluate_node_in_context(arg, *cell) {
|
||||
CalcResult::Range { left, right } => {
|
||||
if left.sheet != right.sheet {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
*cell,
|
||||
"Ranges are in different sheets".to_string(),
|
||||
));
|
||||
}
|
||||
let row1 = left.row;
|
||||
let mut row2 = right.row;
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
match self.evaluate_cell(CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
}) {
|
||||
CalcResult::Number(value) => {
|
||||
values.push(value);
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
CalcResult::EmptyCell => values.push(0.0),
|
||||
_ => {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return Err(error),
|
||||
_ => {
|
||||
return Err(CalcResult::new_error(
|
||||
self.get_array_of_numbers_generic(
|
||||
arg,
|
||||
cell,
|
||||
false, // Do not accept a single number node
|
||||
|| Ok(Some(0.0)), // Treat empty cells as zero
|
||||
|| {
|
||||
Err(CalcResult::new_error(
|
||||
Error::VALUE,
|
||||
*cell,
|
||||
"Expected number".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(values)
|
||||
))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// PMT(rate, nper, pv, [fv], [type])
|
||||
@@ -862,20 +798,28 @@ impl Model {
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
@@ -1393,21 +1337,21 @@ impl Model {
|
||||
CalcResult::Number(result)
|
||||
}
|
||||
|
||||
/// This next three functions deal with Treasure Bills or T-Bills for short
|
||||
/// They are zero-coupon that mature in one year or less.
|
||||
/// Definitions:
|
||||
/// $r$ be the discount rate
|
||||
/// $v$ the face value of the Bill
|
||||
/// $p$ the price of the Bill
|
||||
/// $d_m$ is the number of days from the settlement to maturity
|
||||
/// Then:
|
||||
/// $$ p = v \times\left(1-\frac{d_m}{r}\right) $$
|
||||
/// If d_m is less than 183 days the he Bond Equivalent Yield (BEY, here $y$) is given by:
|
||||
/// $$ y = \frac{F - B}{M}\times \frac{365}{d_m} = \frac{365\times r}{360-r\times d_m}
|
||||
/// If d_m>= 183 days things are a bit more complicated.
|
||||
/// Let $d_e = d_m - 365/2$ if $d_m <= 365$ or $d_e = 183$ if $d_m = 366$.
|
||||
/// $$ v = p\times \left(1+\frac{y}{2}\right)\left(1+d_e\times\frac{y}{365}\right) $$
|
||||
/// Together with the previous relation of $p$ and $v$ gives us a quadratic equation for $y$.
|
||||
// This next three functions deal with Treasure Bills or T-Bills for short
|
||||
// They are zero-coupon that mature in one year or less.
|
||||
// Definitions:
|
||||
// $r$ be the discount rate
|
||||
// $v$ the face value of the Bill
|
||||
// $p$ the price of the Bill
|
||||
// $d_m$ is the number of days from the settlement to maturity
|
||||
// Then:
|
||||
// $$ p = v \times\left(1-\frac{d_m}{r}\right) $$
|
||||
// If d_m is less than 183 days the he Bond Equivalent Yield (BEY, here $y$) is given by:
|
||||
// $$ y = \frac{F - B}{M}\times \frac{365}{d_m} = \frac{365\times r}{360-r\times d_m}
|
||||
// If d_m>= 183 days things are a bit more complicated.
|
||||
// Let $d_e = d_m - 365/2$ if $d_m <= 365$ or $d_e = 183$ if $d_m = 366$.
|
||||
// $$ v = p\times \left(1+\frac{y}{2}\right)\left(1+d_e\times\frac{y}{365}\right) $$
|
||||
// Together with the previous relation of $p$ and $v$ gives us a quadratic equation for $y$.
|
||||
|
||||
// TBILLEQ(settlement, maturity, discount)
|
||||
pub(crate) fn fn_tbilleq(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
|
||||
@@ -838,4 +838,43 @@ impl Model {
|
||||
};
|
||||
CalcResult::Range { left, right }
|
||||
}
|
||||
|
||||
pub(crate) fn fn_formulatext(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() != 1 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
if let CalcResult::Range { left, right } = self.evaluate_node_with_reference(&args[0], cell)
|
||||
{
|
||||
if left.sheet != right.sheet {
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "3D ranges not supported".to_string(),
|
||||
};
|
||||
}
|
||||
if left.row != right.row || left.column != right.column {
|
||||
// FIXME: Implicit intersection or dynamic arrays
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "argument must be a reference to a single cell".to_string(),
|
||||
};
|
||||
}
|
||||
if let Ok(Some(f)) = self.get_cell_formula(left.sheet, left.row, left.column) {
|
||||
CalcResult::String(f)
|
||||
} else {
|
||||
CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Reference does not have a formula".to_string(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Argument must be a reference".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,20 +128,28 @@ impl Model {
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
@@ -195,20 +203,28 @@ impl Model {
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
|
||||
@@ -75,6 +75,7 @@ pub enum Function {
|
||||
|
||||
// Information
|
||||
ErrorType,
|
||||
Formulatext,
|
||||
Isblank,
|
||||
Iserr,
|
||||
Iserror,
|
||||
@@ -122,6 +123,7 @@ pub enum Function {
|
||||
Textbefore,
|
||||
Textjoin,
|
||||
Trim,
|
||||
Unicode,
|
||||
Upper,
|
||||
Value,
|
||||
Valuetotext,
|
||||
@@ -246,7 +248,7 @@ pub enum Function {
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn into_iter() -> IntoIter<Function, 192> {
|
||||
pub fn into_iter() -> IntoIter<Function, 194> {
|
||||
[
|
||||
Function::And,
|
||||
Function::False,
|
||||
@@ -316,6 +318,7 @@ impl Function {
|
||||
Function::Search,
|
||||
Function::Text,
|
||||
Function::Trim,
|
||||
Function::Unicode,
|
||||
Function::Upper,
|
||||
Function::Isnumber,
|
||||
Function::Isnontext,
|
||||
@@ -330,6 +333,7 @@ impl Function {
|
||||
Function::Isodd,
|
||||
Function::Iseven,
|
||||
Function::ErrorType,
|
||||
Function::Formulatext,
|
||||
Function::Isformula,
|
||||
Function::Type,
|
||||
Function::Sheet,
|
||||
@@ -460,6 +464,7 @@ impl Function {
|
||||
Function::Textbefore => "_xlfn.TEXTBEFORE".to_string(),
|
||||
Function::Textafter => "_xlfn.TEXTAFTER".to_string(),
|
||||
Function::Textjoin => "_xlfn.TEXTJOIN".to_string(),
|
||||
Function::Unicode => "_xlfn.UNICODE".to_string(),
|
||||
Function::Rri => "_xlfn.RRI".to_string(),
|
||||
Function::Pduration => "_xlfn.PDURATION".to_string(),
|
||||
Function::Bitand => "_xlfn.BITAND".to_string(),
|
||||
@@ -479,6 +484,7 @@ impl Function {
|
||||
Function::Valuetotext => "_xlfn.VALUETOTEXT".to_string(),
|
||||
Function::Isformula => "_xlfn.ISFORMULA".to_string(),
|
||||
Function::Sheet => "_xlfn.SHEET".to_string(),
|
||||
Function::Formulatext => "_xlfn.FORMULATEXT".to_string(),
|
||||
_ => self.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -567,6 +573,7 @@ impl Function {
|
||||
"SEARCH" => Some(Function::Search),
|
||||
"TEXT" => Some(Function::Text),
|
||||
"TRIM" => Some(Function::Trim),
|
||||
"UNICODE" | "_XLFN.UNICODE" => Some(Function::Unicode),
|
||||
"UPPER" => Some(Function::Upper),
|
||||
|
||||
"REPT" => Some(Function::Rept),
|
||||
@@ -588,6 +595,7 @@ impl Function {
|
||||
"ISODD" => Some(Function::Isodd),
|
||||
"ISEVEN" => Some(Function::Iseven),
|
||||
"ERROR.TYPE" => Some(Function::ErrorType),
|
||||
"FORMULATEXT" | "_XLFN.FORMULATEXT" => Some(Function::Formulatext),
|
||||
"ISFORMULA" | "_XLFN.ISFORMULA" => Some(Function::Isformula),
|
||||
"TYPE" => Some(Function::Type),
|
||||
"SHEET" | "_XLFN.SHEET" => Some(Function::Sheet),
|
||||
@@ -779,6 +787,7 @@ impl fmt::Display for Function {
|
||||
Function::Search => write!(f, "SEARCH"),
|
||||
Function::Text => write!(f, "TEXT"),
|
||||
Function::Trim => write!(f, "TRIM"),
|
||||
Function::Unicode => write!(f, "UNICODE"),
|
||||
Function::Upper => write!(f, "UPPER"),
|
||||
Function::Isnumber => write!(f, "ISNUMBER"),
|
||||
Function::Isnontext => write!(f, "ISNONTEXT"),
|
||||
@@ -793,6 +802,7 @@ impl fmt::Display for Function {
|
||||
Function::Isodd => write!(f, "ISODD"),
|
||||
Function::Iseven => write!(f, "ISEVEN"),
|
||||
Function::ErrorType => write!(f, "ERROR.TYPE"),
|
||||
Function::Formulatext => write!(f, "FORMULATEXT"),
|
||||
Function::Isformula => write!(f, "ISFORMULA"),
|
||||
Function::Type => write!(f, "TYPE"),
|
||||
Function::Sheet => write!(f, "SHEET"),
|
||||
@@ -1012,6 +1022,7 @@ impl Model {
|
||||
Function::Search => self.fn_search(args, cell),
|
||||
Function::Text => self.fn_text(args, cell),
|
||||
Function::Trim => self.fn_trim(args, cell),
|
||||
Function::Unicode => self.fn_unicode(args, cell),
|
||||
Function::Upper => self.fn_upper(args, cell),
|
||||
// Information
|
||||
Function::Isnumber => self.fn_isnumber(args, cell),
|
||||
@@ -1027,6 +1038,7 @@ impl Model {
|
||||
Function::Isodd => self.fn_isodd(args, cell),
|
||||
Function::Iseven => self.fn_iseven(args, cell),
|
||||
Function::ErrorType => self.fn_errortype(args, cell),
|
||||
Function::Formulatext => self.fn_formulatext(args, cell),
|
||||
Function::Isformula => self.fn_isformula(args, cell),
|
||||
Function::Type => self.fn_type(args, cell),
|
||||
Function::Sheet => self.fn_sheet(args, cell),
|
||||
@@ -1148,6 +1160,7 @@ impl Model {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
|
||||
@@ -381,11 +381,16 @@ impl Model {
|
||||
let right_row = first_range.right.row;
|
||||
let right_column = first_range.right.column;
|
||||
|
||||
let dimension = self
|
||||
.workbook
|
||||
.worksheet(first_range.left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension();
|
||||
let dimension = match self.workbook.worksheet(first_range.left.sheet) {
|
||||
Ok(s) => s.dimension(),
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", first_range.left.sheet),
|
||||
)
|
||||
}
|
||||
};
|
||||
let max_row = dimension.max_row;
|
||||
let max_column = dimension.max_column;
|
||||
|
||||
@@ -526,20 +531,28 @@ impl Model {
|
||||
let mut right_column = sum_range.right.column;
|
||||
|
||||
if left_row == 1 && right_row == LAST_ROW {
|
||||
right_row = self
|
||||
.workbook
|
||||
.worksheet(sum_range.left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
right_row = match self.workbook.worksheet(sum_range.left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", sum_range.left.sheet),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
if left_column == 1 && right_column == LAST_COLUMN {
|
||||
right_column = self
|
||||
.workbook
|
||||
.worksheet(sum_range.left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
right_column = match self.workbook.worksheet(sum_range.left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return Err(CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", sum_range.left.sheet),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for row in left_row..right_row + 1 {
|
||||
|
||||
@@ -53,8 +53,13 @@ impl Model {
|
||||
false
|
||||
}
|
||||
|
||||
fn cell_hidden_status(&self, sheet_index: u32, row: i32, column: i32) -> CellTableStatus {
|
||||
let worksheet = self.workbook.worksheet(sheet_index).expect("");
|
||||
fn cell_hidden_status(
|
||||
&self,
|
||||
sheet_index: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<CellTableStatus, String> {
|
||||
let worksheet = self.workbook.worksheet(sheet_index)?;
|
||||
let mut hidden = false;
|
||||
for row_style in &worksheet.rows {
|
||||
if row_style.r == row {
|
||||
@@ -63,13 +68,13 @@ impl Model {
|
||||
}
|
||||
}
|
||||
if !hidden {
|
||||
return CellTableStatus::Normal;
|
||||
return Ok(CellTableStatus::Normal);
|
||||
}
|
||||
// The row is hidden we need to know if the table has filters
|
||||
if self.get_table_for_cell(sheet_index, row, column) {
|
||||
CellTableStatus::Filtered
|
||||
Ok(CellTableStatus::Filtered)
|
||||
} else {
|
||||
CellTableStatus::Hidden
|
||||
Ok(CellTableStatus::Hidden)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +148,11 @@ impl Model {
|
||||
let column2 = right.column;
|
||||
|
||||
for row in row1..=row2 {
|
||||
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
|
||||
let cell_status = self
|
||||
.cell_hidden_status(left.sheet, row, column1)
|
||||
.map_err(|message| {
|
||||
CalcResult::new_error(Error::ERROR, cell, message)
|
||||
})?;
|
||||
if cell_status == CellTableStatus::Filtered {
|
||||
continue;
|
||||
}
|
||||
@@ -380,7 +389,14 @@ impl Model {
|
||||
let column2 = right.column;
|
||||
|
||||
for row in row1..=row2 {
|
||||
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
|
||||
let cell_status = match self
|
||||
.cell_hidden_status(left.sheet, row, column1)
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(message) => {
|
||||
return CalcResult::new_error(Error::ERROR, cell, message);
|
||||
}
|
||||
};
|
||||
if cell_status == CellTableStatus::Filtered {
|
||||
continue;
|
||||
}
|
||||
@@ -449,7 +465,14 @@ impl Model {
|
||||
let column2 = right.column;
|
||||
|
||||
for row in row1..=row2 {
|
||||
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
|
||||
let cell_status = match self
|
||||
.cell_hidden_status(left.sheet, row, column1)
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(message) => {
|
||||
return CalcResult::new_error(Error::ERROR, cell, message);
|
||||
}
|
||||
};
|
||||
if cell_status == CellTableStatus::Filtered {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ impl Model {
|
||||
/// * If find_text does not appear in within_text, FIND and FINDB return the #VALUE! error value.
|
||||
/// * If start_num is not greater than zero, FIND and FINDB return the #VALUE! error value.
|
||||
/// * If start_num is greater than the length of within_text, FIND and FINDB return the #VALUE! error value.
|
||||
/// NB: FINDB is not implemented. It is the same as FIND function unless locale is a DBCS (Double Byte Character Set)
|
||||
/// NB: FINDB is not implemented. It is the same as FIND function unless locale is a DBCS (Double Byte Character Set)
|
||||
pub(crate) fn fn_find(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() < 2 || args.len() > 3 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
@@ -203,7 +203,7 @@ impl Model {
|
||||
/// Same API as FIND but:
|
||||
/// * Allows wildcards
|
||||
/// * It is case insensitive
|
||||
/// SEARCH(find_text, within_text, [start_num])
|
||||
/// SEARCH(find_text, within_text, [start_num])
|
||||
pub(crate) fn fn_search(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() < 2 || args.len() > 3 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
@@ -342,6 +342,53 @@ impl Model {
|
||||
CalcResult::new_args_number_error(cell)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_unicode(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() == 1 {
|
||||
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
||||
CalcResult::Number(v) => format!("{}", v),
|
||||
CalcResult::String(v) => v,
|
||||
CalcResult::Boolean(b) => {
|
||||
if b {
|
||||
"TRUE".to_string()
|
||||
} else {
|
||||
"FALSE".to_string()
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => return error,
|
||||
CalcResult::Range { .. } => {
|
||||
// Implicit Intersection not implemented
|
||||
return CalcResult::Error {
|
||||
error: Error::NIMPL,
|
||||
origin: cell,
|
||||
message: "Implicit Intersection not implemented".to_string(),
|
||||
};
|
||||
}
|
||||
CalcResult::EmptyCell | CalcResult::EmptyArg => {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Empty cell".to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match s.chars().next() {
|
||||
Some(c) => {
|
||||
let unicode_number = c as u32;
|
||||
return CalcResult::Number(unicode_number as f64);
|
||||
}
|
||||
None => {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Empty cell".to_string(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
CalcResult::new_args_number_error(cell)
|
||||
}
|
||||
|
||||
pub(crate) fn fn_upper(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
if args.len() == 1 {
|
||||
let s = match self.evaluate_node_in_context(&args[0], cell) {
|
||||
@@ -503,7 +550,7 @@ impl Model {
|
||||
}
|
||||
result.push(ch);
|
||||
}
|
||||
return CalcResult::String(result.chars().rev().collect::<String>());
|
||||
CalcResult::String(result.chars().rev().collect::<String>())
|
||||
}
|
||||
|
||||
pub(crate) fn fn_mid(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
||||
@@ -888,20 +935,28 @@ impl Model {
|
||||
let column1 = left.column;
|
||||
let mut column2 = right.column;
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
for row in row1..row2 + 1 {
|
||||
for column in column1..(column2 + 1) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use regex::{escape, Regex};
|
||||
#[cfg(feature = "use_regex_lite")]
|
||||
use regex_lite as regex;
|
||||
|
||||
use crate::{calc_result::CalcResult, expressions::token::is_english_error_string};
|
||||
|
||||
@@ -25,9 +26,9 @@ pub(crate) fn values_are_equal(left: &CalcResult, right: &CalcResult) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// In Excel there are two ways of comparing cell values.
|
||||
/// The old school comparison valid in formulas like D3 < D4 or HLOOKUP,... cast empty cells into empty strings or 0
|
||||
/// For the new formulas like XLOOKUP or SORT an empty cell is always larger than anything else.
|
||||
// In Excel there are two ways of comparing cell values.
|
||||
// The old school comparison valid in formulas like D3 < D4 or HLOOKUP,... cast empty cells into empty strings or 0
|
||||
// For the new formulas like XLOOKUP or SORT an empty cell is always larger than anything else.
|
||||
|
||||
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
|
||||
pub(crate) fn compare_values(left: &CalcResult, right: &CalcResult) -> i32 {
|
||||
@@ -86,7 +87,7 @@ pub(crate) fn from_wildcard_to_regex(
|
||||
exact: bool,
|
||||
) -> Result<regex::Regex, regex::Error> {
|
||||
// 1. Escape all
|
||||
let reg = &escape(wildcard);
|
||||
let reg = ®ex::escape(wildcard);
|
||||
|
||||
// 2. We convert the escaped '?' into '.' (matches a single character)
|
||||
let reg = ®.replace("\\?", ".");
|
||||
@@ -109,13 +110,13 @@ pub(crate) fn from_wildcard_to_regex(
|
||||
|
||||
// And we have a valid Perl regex! (As Kim Kardashian said before me: "I know, right?")
|
||||
if exact {
|
||||
return Regex::new(&format!("^{}$", reg));
|
||||
return regex::Regex::new(&format!("^{}$", reg));
|
||||
}
|
||||
Regex::new(reg)
|
||||
regex::Regex::new(reg)
|
||||
}
|
||||
|
||||
/// NUMBERS ///
|
||||
///*********///
|
||||
// NUMBERS ///
|
||||
//*********///
|
||||
|
||||
// It could be either the number or a string representation of the number
|
||||
// In the rest of the cases calc_result needs to be a number (cannot be the string "23", for instance)
|
||||
@@ -180,8 +181,8 @@ fn result_is_not_equal_to_number(calc_result: &CalcResult, target: f64) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
/// BOOLEANS ///
|
||||
///**********///
|
||||
// BOOLEANS ///
|
||||
//**********///
|
||||
|
||||
// Booleans have to be "exactly" equal
|
||||
fn result_is_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
|
||||
@@ -198,12 +199,12 @@ fn result_is_not_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// STRINGS ///
|
||||
///*********///
|
||||
// STRINGS ///
|
||||
//*********///
|
||||
|
||||
/// Note that strings are case insensitive. `target` must always be lower case.
|
||||
// Note that strings are case insensitive. `target` must always be lower case.
|
||||
|
||||
pub(crate) fn result_matches_regex(calc_result: &CalcResult, reg: &Regex) -> bool {
|
||||
pub(crate) fn result_matches_regex(calc_result: &CalcResult, reg: ®ex::Regex) -> bool {
|
||||
match calc_result {
|
||||
CalcResult::String(s) => reg.is_match(&s.to_lowercase()),
|
||||
_ => false,
|
||||
@@ -269,8 +270,8 @@ fn result_is_greater_or_equal_than_string(calc_result: &CalcResult, target: &str
|
||||
}
|
||||
}
|
||||
|
||||
/// ERRORS ///
|
||||
///********///
|
||||
// ERRORS ///
|
||||
//********///
|
||||
|
||||
fn result_is_equal_to_error(calc_result: &CalcResult, target: &str) -> bool {
|
||||
match calc_result {
|
||||
@@ -286,8 +287,8 @@ fn result_is_not_equal_to_error(calc_result: &CalcResult, target: &str) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
/// EMPTY ///
|
||||
///*******///
|
||||
// EMPTY ///
|
||||
//*******///
|
||||
|
||||
// Note that these two are not inverse of each other.
|
||||
// In particular, you can never match an empty cell.
|
||||
|
||||
@@ -251,20 +251,28 @@ impl Model {
|
||||
let column1 = left.column;
|
||||
|
||||
if row1 == 1 && row2 == LAST_ROW {
|
||||
row2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_row;
|
||||
row2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_row,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
if column1 == 1 && column2 == LAST_COLUMN {
|
||||
column2 = self
|
||||
.workbook
|
||||
.worksheet(left.sheet)
|
||||
.expect("Sheet expected during evaluation.")
|
||||
.dimension()
|
||||
.max_column;
|
||||
column2 = match self.workbook.worksheet(left.sheet) {
|
||||
Ok(s) => s.dimension().max_column,
|
||||
Err(_) => {
|
||||
return CalcResult::new_error(
|
||||
Error::ERROR,
|
||||
cell,
|
||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
let left = CellReferenceIndex {
|
||||
sheet: left.sheet,
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct Language {
|
||||
pub errors: Errors,
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
||||
bitcode::decode(include_bytes!("language.bin")).expect("Failed parsing language file")
|
||||
});
|
||||
|
||||
@@ -57,4 +57,6 @@ 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;
|
||||
|
||||
@@ -65,6 +65,7 @@ pub struct DecimalFormats {
|
||||
pub standard: String,
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
static LOCALES: Lazy<HashMap<String, Locale>> =
|
||||
Lazy::new(|| bitcode::decode(include_bytes!("locales.bin")).expect("Failed parsing locale"));
|
||||
|
||||
|
||||
@@ -7,19 +7,15 @@ use crate::{
|
||||
calc_result::{CalcResult, Range},
|
||||
cell::CellValue,
|
||||
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::{
|
||||
parser::{
|
||||
move_formula::{move_formula, MoveContext},
|
||||
stringify::{to_rc_format, to_string},
|
||||
Node, Parser,
|
||||
},
|
||||
utils::is_valid_column_number,
|
||||
token::{get_error_by_name, Error, OpCompare, OpProduct, OpSum, OpUnary},
|
||||
types::*,
|
||||
utils::{self, is_valid_column_number, is_valid_row, parse_reference_a1},
|
||||
},
|
||||
formatter::{
|
||||
format::{format_number, parse_formatted_number},
|
||||
@@ -45,6 +41,7 @@ pub use crate::mock_time::get_milliseconds_since_epoch;
|
||||
/// * Or mocked for tests
|
||||
#[cfg(not(test))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::expect_used)]
|
||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
SystemTime::now()
|
||||
@@ -94,11 +91,11 @@ pub(crate) enum ParsedDefinedName {
|
||||
/// * The Locale: a parsed version of the Workbook's locale
|
||||
/// * The Timezone: an object representing the Workbook's timezone
|
||||
/// * The language. Note that the timezone and the locale belong to the workbook while
|
||||
/// the language can be different for different users looking _at the same_ workbook.
|
||||
/// the language can be different for different users looking _at the same_ workbook.
|
||||
/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only)
|
||||
/// * A list of cells with its status (evaluating, evaluated, not evaluated)
|
||||
/// * A dictionary with the shared strings and their indices.
|
||||
/// This is an optimization for large files (~1 million rows)
|
||||
/// This is an optimization for large files (~1 million rows)
|
||||
pub struct Model {
|
||||
/// A Rust internal representation of an Excel workbook
|
||||
pub workbook: Workbook,
|
||||
@@ -118,6 +115,8 @@ pub struct Model {
|
||||
pub(crate) language: Language,
|
||||
/// The timezone used to evaluate the model
|
||||
pub(crate) 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
|
||||
@@ -531,6 +530,7 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
fn cell_reference_to_string(
|
||||
&self,
|
||||
cell_reference: &CellReferenceIndex,
|
||||
@@ -546,6 +546,7 @@ impl Model {
|
||||
/// Sets `result` in the cell given by `sheet` sheet index, row and column
|
||||
/// Note that will panic if the cell does not exist
|
||||
/// It will do nothing if the cell does not have a formula
|
||||
#[allow(clippy::expect_used)]
|
||||
fn set_cell_value(&mut self, cell_reference: CellReferenceIndex, result: &CalcResult) {
|
||||
let CellReferenceIndex { sheet, column, row } = cell_reference;
|
||||
let cell = &self.workbook.worksheets[sheet as usize].sheet_data[&row][&column];
|
||||
@@ -681,6 +682,13 @@ impl Model {
|
||||
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 {
|
||||
use Cell::*;
|
||||
match cell {
|
||||
@@ -739,6 +747,29 @@ impl Model {
|
||||
self.workbook.worksheet(sheet)?.is_empty_cell(row, column)
|
||||
}
|
||||
|
||||
/// Returns 'true' if the cell belongs to any Merged cells
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ironcalc_base::Model;
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||
/// model.merge_cells(0, "A1:D5");
|
||||
/// assert_eq!(model.is_part_of_merged_cells(0, 1, 2)?, true);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn is_part_of_merged_cells(
|
||||
&self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
) -> Result<bool, String> {
|
||||
self.workbook
|
||||
.worksheet(sheet)?
|
||||
.is_part_of_merged_cells(row, column)
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate_cell(&mut self, cell_reference: CellReferenceIndex) -> CalcResult {
|
||||
let row_data = match self.workbook.worksheets[cell_reference.sheet as usize]
|
||||
.sheet_data
|
||||
@@ -870,6 +901,7 @@ impl Model {
|
||||
.map_err(|_| format!("Invalid timezone: {}", workbook.settings.tz))?;
|
||||
|
||||
// FIXME: Add support for display languages
|
||||
#[allow(clippy::expect_used)]
|
||||
let language = get_language("en").expect("").clone();
|
||||
let mut shared_strings = HashMap::new();
|
||||
for (index, s) in workbook.shared_strings.iter().enumerate() {
|
||||
@@ -886,6 +918,7 @@ impl Model {
|
||||
language,
|
||||
locale,
|
||||
tz,
|
||||
view_id: 0,
|
||||
};
|
||||
|
||||
model.parse_formulas();
|
||||
@@ -1194,10 +1227,10 @@ impl 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, "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());
|
||||
///
|
||||
/// 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());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@@ -1208,23 +1241,38 @@ impl Model {
|
||||
/// * [Model::update_cell_with_number()]
|
||||
/// * [Model::update_cell_with_bool()]
|
||||
/// * [Model::update_cell_with_formula()]
|
||||
pub fn update_cell_with_text(&mut self, sheet: u32, row: i32, column: i32, value: &str) {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||
pub fn update_cell_with_text(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: &str,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
||||
let new_style_index;
|
||||
if common::value_needs_quoting(value, &self.language) {
|
||||
new_style_index = self
|
||||
.workbook
|
||||
.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) {
|
||||
new_style_index = self
|
||||
.workbook
|
||||
.styles
|
||||
.get_style_without_quote_prefix(style_index);
|
||||
.get_style_without_quote_prefix(style_index)?;
|
||||
} else {
|
||||
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
|
||||
@@ -1237,10 +1285,10 @@ impl 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, "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());
|
||||
///
|
||||
/// 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());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@@ -1251,17 +1299,31 @@ impl Model {
|
||||
/// * [Model::update_cell_with_number()]
|
||||
/// * [Model::update_cell_with_text()]
|
||||
/// * [Model::update_cell_with_formula()]
|
||||
pub fn update_cell_with_bool(&mut self, sheet: u32, row: i32, column: i32, value: bool) {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||
pub fn update_cell_with_bool(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: bool,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
|
||||
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) {
|
||||
self.workbook
|
||||
.styles
|
||||
.get_style_without_quote_prefix(style_index)
|
||||
.get_style_without_quote_prefix(style_index)?
|
||||
} else {
|
||||
style_index
|
||||
};
|
||||
let worksheet = &mut self.workbook.worksheets[sheet as usize];
|
||||
worksheet.set_cell_with_boolean(row, column, value, new_style_index);
|
||||
self.set_cell_with_boolean(sheet, row, column, value, new_style_index)
|
||||
}
|
||||
|
||||
/// Updates the value of a cell with a number
|
||||
@@ -1274,10 +1336,10 @@ impl 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, "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());
|
||||
///
|
||||
/// 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());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@@ -1288,17 +1350,30 @@ impl Model {
|
||||
/// * [Model::update_cell_with_text()]
|
||||
/// * [Model::update_cell_with_bool()]
|
||||
/// * [Model::update_cell_with_formula()]
|
||||
pub fn update_cell_with_number(&mut self, sheet: u32, row: i32, column: i32, value: f64) {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column);
|
||||
pub fn update_cell_with_number(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: f64,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
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) {
|
||||
self.workbook
|
||||
.styles
|
||||
.get_style_without_quote_prefix(style_index)
|
||||
.get_style_without_quote_prefix(style_index)?
|
||||
} else {
|
||||
style_index
|
||||
};
|
||||
let worksheet = &mut self.workbook.worksheets[sheet as usize];
|
||||
worksheet.set_cell_with_number(row, column, value, new_style_index);
|
||||
self.set_cell_with_number(sheet, row, column, value, new_style_index)
|
||||
}
|
||||
|
||||
/// Updates the formula of given cell
|
||||
@@ -1312,11 +1387,11 @@ impl 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, "=A2*2".to_string());
|
||||
/// model.set_user_input(sheet, row, column, "=A2*2".to_string())?;
|
||||
/// model.evaluate();
|
||||
/// 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();
|
||||
/// assert_eq!(model.get_cell_content(sheet, row, column)?, "=A3*2".to_string());
|
||||
/// # Ok(())
|
||||
@@ -1335,12 +1410,18 @@ impl Model {
|
||||
column: i32,
|
||||
formula: String,
|
||||
) -> Result<(), String> {
|
||||
let mut style_index = self.get_cell_style_index(sheet, row, column);
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
let mut style_index = self.get_cell_style_index(sheet, row, column)?;
|
||||
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||
style_index = self
|
||||
.workbook
|
||||
.styles
|
||||
.get_style_without_quote_prefix(style_index);
|
||||
.get_style_without_quote_prefix(style_index)?;
|
||||
}
|
||||
let formula = formula
|
||||
.strip_prefix('=')
|
||||
@@ -1380,31 +1461,44 @@ impl Model {
|
||||
/// * [Model::update_cell_with_number()]
|
||||
/// * [Model::update_cell_with_bool()]
|
||||
/// * [Model::update_cell_with_text()]
|
||||
pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, value: String) {
|
||||
pub fn set_user_input(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: String,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
// 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('\'') {
|
||||
// First check if it needs quoting
|
||||
let new_style = if common::value_needs_quoting(new_value, &self.language) {
|
||||
self.workbook
|
||||
.styles
|
||||
.get_style_with_quote_prefix(style_index)
|
||||
.get_style_with_quote_prefix(style_index)?
|
||||
} else {
|
||||
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 {
|
||||
let mut new_style_index = style_index;
|
||||
if self.workbook.styles.style_is_quote_prefix(style_index) {
|
||||
new_style_index = self
|
||||
.workbook
|
||||
.styles
|
||||
.get_style_without_quote_prefix(style_index);
|
||||
.get_style_without_quote_prefix(style_index)?;
|
||||
}
|
||||
if let Some(formula) = value.strip_prefix('=') {
|
||||
let formula_index = self
|
||||
.set_cell_with_formula(sheet, row, column, formula, new_style_index)
|
||||
.expect("could not set the cell formula");
|
||||
let formula_index =
|
||||
self.set_cell_with_formula(sheet, row, column, formula, new_style_index)?;
|
||||
// Update the style if needed
|
||||
let cell = CellReferenceIndex { sheet, row, column };
|
||||
let parsed_formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
|
||||
@@ -1412,15 +1506,11 @@ impl Model {
|
||||
let new_style_index = self
|
||||
.workbook
|
||||
.styles
|
||||
.get_style_with_format(new_style_index, &units.get_num_fmt());
|
||||
let style = self.workbook.styles.get_style(new_style_index);
|
||||
self.set_cell_style(sheet, row, column, &style)
|
||||
.expect("Failed setting the style");
|
||||
.get_style_with_format(new_style_index, &units.get_num_fmt())?;
|
||||
let style = self.workbook.styles.get_style(new_style_index)?;
|
||||
self.set_cell_style(sheet, row, column, &style)?
|
||||
}
|
||||
} else {
|
||||
let worksheets = &mut self.workbook.worksheets;
|
||||
let worksheet = &mut worksheets[sheet as usize];
|
||||
|
||||
// The list of currencies is '$', '€' and the local currency
|
||||
let mut currencies = vec!["$", "€"];
|
||||
let currency = &self.locale.currency.symbol;
|
||||
@@ -1433,35 +1523,39 @@ impl Model {
|
||||
// Should not apply the format in the following cases:
|
||||
// - we assign a date to already date-formatted cell
|
||||
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));
|
||||
if should_apply_format {
|
||||
new_style_index = self
|
||||
.workbook
|
||||
.styles
|
||||
.get_style_with_format(new_style_index, &num_fmt);
|
||||
.get_style_with_format(new_style_index, &num_fmt)?;
|
||||
}
|
||||
}
|
||||
worksheet.set_cell_with_number(row, column, v, new_style_index);
|
||||
return;
|
||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||
worksheet.set_cell_with_number(row, column, v, new_style_index)?;
|
||||
return Ok(());
|
||||
}
|
||||
// We try to parse as boolean
|
||||
if let Ok(v) = value.to_lowercase().parse::<bool>() {
|
||||
worksheet.set_cell_with_boolean(row, column, v, new_style_index);
|
||||
return;
|
||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||
worksheet.set_cell_with_boolean(row, column, v, new_style_index)?;
|
||||
return Ok(());
|
||||
}
|
||||
// Check is it is error value
|
||||
let upper = value.to_uppercase();
|
||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||
match get_error_by_name(&upper, &self.language) {
|
||||
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 => {
|
||||
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(
|
||||
@@ -1502,24 +1596,66 @@ impl Model {
|
||||
self.parsed_formulas[sheet as usize].push(parsed_formula);
|
||||
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)
|
||||
}
|
||||
|
||||
fn set_cell_with_string(&mut self, sheet: u32, row: i32, column: i32, value: &str, style: i32) {
|
||||
let worksheets = &mut self.workbook.worksheets;
|
||||
let worksheet = &mut worksheets[sheet as usize];
|
||||
fn set_cell_with_string(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: &str,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
match self.shared_strings.get(value) {
|
||||
Some(string_index) => {
|
||||
worksheet.set_cell_with_string(row, column, *string_index as i32, style);
|
||||
self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
|
||||
row,
|
||||
column,
|
||||
*string_index as i32,
|
||||
style,
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
let string_index = self.workbook.shared_strings.len();
|
||||
self.workbook.shared_strings.push(value.to_string());
|
||||
self.shared_strings.insert(value.to_string(), string_index);
|
||||
worksheet.set_cell_with_string(row, column, string_index as i32, style);
|
||||
self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
|
||||
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
|
||||
@@ -1586,7 +1722,7 @@ impl Model {
|
||||
) -> Result<String, String> {
|
||||
match self.workbook.worksheet(sheet_index)?.cell(row, column) {
|
||||
Some(cell) => {
|
||||
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
|
||||
let format = self.get_style_for_cell(sheet_index, row, column)?.num_fmt;
|
||||
let formatted_value =
|
||||
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
|
||||
format_number(value, &format, &self.locale).text
|
||||
@@ -1689,7 +1825,7 @@ impl Model {
|
||||
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);
|
||||
.cell_clear_contents(row, column)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1724,43 +1860,40 @@ impl Model {
|
||||
}
|
||||
|
||||
/// Returns the style index for cell (`sheet`, `row`, `column`)
|
||||
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> i32 {
|
||||
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> Result<i32, String> {
|
||||
// First check the cell, then row, the column
|
||||
let cell = self
|
||||
.workbook
|
||||
.worksheet(sheet)
|
||||
.expect("Invalid sheet")
|
||||
.cell(row, column);
|
||||
let cell = self.workbook.worksheet(sheet)?.cell(row, column);
|
||||
|
||||
match cell {
|
||||
Some(cell) => cell.get_style(),
|
||||
Some(cell) => Ok(cell.get_style()),
|
||||
None => {
|
||||
let rows = &self.workbook.worksheets[sheet as usize].rows;
|
||||
let rows = &self.workbook.worksheet(sheet)?.rows;
|
||||
for r in rows {
|
||||
if r.r == row {
|
||||
if r.custom_format {
|
||||
return r.s;
|
||||
return Ok(r.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
let cols = &self.workbook.worksheets[sheet as usize].cols;
|
||||
let cols = &self.workbook.worksheet(sheet)?.cols;
|
||||
for c in cols.iter() {
|
||||
let min = c.min;
|
||||
let max = c.max;
|
||||
if column >= min && column <= max {
|
||||
return c.style.unwrap_or(0);
|
||||
return Ok(c.style.unwrap_or(0));
|
||||
}
|
||||
}
|
||||
0
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the style for cell (`sheet`, `row`, `column`)
|
||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Style {
|
||||
self.workbook
|
||||
.styles
|
||||
.get_style(self.get_cell_style_index(sheet, row, column))
|
||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
|
||||
let style_index = self.get_cell_style_index(sheet, row, column)?;
|
||||
let style = self.workbook.styles.get_style(style_index)?;
|
||||
Ok(style)
|
||||
}
|
||||
|
||||
/// Returns an internal binary representation of the workbook
|
||||
@@ -1800,7 +1933,7 @@ impl Model {
|
||||
Some(formula) => formula,
|
||||
None => self.get_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 {
|
||||
cell_markup = format!("**{cell_markup}**")
|
||||
}
|
||||
@@ -1857,6 +1990,7 @@ impl Model {
|
||||
/// Sets the number of frozen rows to `frozen_rows` in the workbook.
|
||||
/// Fails if `frozen`_rows` is either too small (<0) or too large (>LAST_ROW)`
|
||||
pub fn set_frozen_rows(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> {
|
||||
// TODO: What is frozen rows and do we need to take of this if row we are frozing is part of merge cells ?
|
||||
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
||||
if frozen_rows < 0 {
|
||||
return Err("Frozen rows cannot be negative".to_string());
|
||||
@@ -1874,6 +2008,7 @@ impl Model {
|
||||
/// Sets the number of frozen columns to `frozen_column` in the workbook.
|
||||
/// Fails if `frozen`_columns` is either too small (<0) or too large (>LAST_COLUMN)`
|
||||
pub fn set_frozen_columns(&mut self, sheet: u32, frozen_columns: i32) -> Result<(), String> {
|
||||
// TODO: What is frozen columns and do we need to take of this if column we are frozing is part of merge cells ?
|
||||
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
||||
if frozen_columns < 0 {
|
||||
return Err("Frozen columns cannot be negative".to_string());
|
||||
@@ -1915,10 +2050,169 @@ impl Model {
|
||||
.worksheet_mut(sheet)?
|
||||
.set_row_height(column, height)
|
||||
}
|
||||
|
||||
fn parse_merged_range(&mut self, range: &str) -> Result<(i32, i32, i32, i32), String> {
|
||||
let parts: Vec<&str> = range.split(':').collect();
|
||||
if parts.len() == 1 {
|
||||
Err(format!("Invalid range: '{}'", range))
|
||||
} else if parts.len() == 2 {
|
||||
match (parse_reference_a1(parts[0]), parse_reference_a1(parts[1])) {
|
||||
(Some(left), Some(right)) => {
|
||||
return Ok((left.row, left.column, right.row, right.column));
|
||||
}
|
||||
_ => return Err(format!("Invalid range: '{}'", range)),
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Invalid range: '{}'", range));
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing public APIS related to Merge cells handling
|
||||
|
||||
/// Merges given selected cells
|
||||
/// If no overlap, it will create that merged cells with left most top cell value representing the whole merged cells
|
||||
/// If new merge cells creation overlaps with any of the existing merged cells, Overlapped merged cells gets unmerged
|
||||
/// and new merge cells gets added
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ironcalc_base::Model;
|
||||
/// # use ironcalc_base::cell::CellValue;
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||
/// model.merge_cells(0, "D4:F6").unwrap();
|
||||
/// model.merge_cells(0, "A1:B4").unwrap();
|
||||
/// assert_eq!(model.workbook.worksheet(0).unwrap().merged_cells_list.len(), 2);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
/// * [Model::update_cell_with_formula()]
|
||||
/// * [Model::update_cell_with_number()]
|
||||
/// * [Model::update_cell_with_bool()]
|
||||
/// * [Model::update_cell_with_text()]
|
||||
pub fn merge_cells(&mut self, sheet: u32, range_ref: &str) -> Result<(), String> {
|
||||
match self.parse_merged_range(range_ref) {
|
||||
Ok(parsed_merge_cell_range) => {
|
||||
// ATTENTION 2: Below thing we can support here but keeping it simple
|
||||
// Web or different client needs to keep this in mind
|
||||
// User can give errored parse ranges like C3:A1
|
||||
// Where col_start and row_start and is greated then col_end and row_end
|
||||
// Return error in these scenario
|
||||
if parsed_merge_cell_range.0 > parsed_merge_cell_range.2
|
||||
|| parsed_merge_cell_range.1 > parsed_merge_cell_range.3
|
||||
{
|
||||
return Err(
|
||||
"Invalid parse range. Merge Mother cell always be top left cell"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut merged_cells_overlaped_list: Vec<bool> = Vec::new();
|
||||
// checking whether our new range overlaps with any of the already existing merged cells
|
||||
// if so, need to unmerge those and create this new one
|
||||
{
|
||||
let worksheet = self.workbook.worksheet(sheet)?;
|
||||
let merged_cells = worksheet.get_merged_cells_list();
|
||||
|
||||
for merge_node in merged_cells {
|
||||
// checking whether any overlapping exist with this merge cell
|
||||
if !(parsed_merge_cell_range.1 > merge_node.3
|
||||
|| parsed_merge_cell_range.3 < merge_node.1
|
||||
|| parsed_merge_cell_range.0 > merge_node.2
|
||||
|| parsed_merge_cell_range.2 < merge_node.0)
|
||||
{
|
||||
// overlap has happened
|
||||
merged_cells_overlaped_list.push(true);
|
||||
} else {
|
||||
merged_cells_overlaped_list.push(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !merged_cells_overlaped_list.is_empty() {
|
||||
// Lets take Mutable ref to Merge cell and deletes all those nodes which has overlapped
|
||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||
let merged_cells_list_mut = worksheet.get_merged_cells_list_mut();
|
||||
let mut merged_cells_overlaped_list_iter = merged_cells_overlaped_list.iter();
|
||||
merged_cells_list_mut
|
||||
.retain(|_| !(*merged_cells_overlaped_list_iter.next().unwrap()))
|
||||
}
|
||||
|
||||
// Now need to update (n*m - 1) cells with empty cell ( except the Mother cell )
|
||||
for row_index in parsed_merge_cell_range.0..=parsed_merge_cell_range.2 {
|
||||
for col_index in parsed_merge_cell_range.1..=parsed_merge_cell_range.3 {
|
||||
// skip Mother cell
|
||||
if row_index == parsed_merge_cell_range.0
|
||||
&& col_index == parsed_merge_cell_range.2
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//update the node with empty cell
|
||||
{
|
||||
self.workbook.worksheet_mut(sheet)?.update_cell(
|
||||
row_index,
|
||||
col_index,
|
||||
Cell::EmptyCell { s: 0 },
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_merged_cells = MergedCells::new(parsed_merge_cell_range);
|
||||
{
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.merged_cells_list
|
||||
.push(new_merged_cells);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unmerges a given/selected merged cells
|
||||
/// Once unmerged, only top most left corner value gets retained and all the others will have empty cell
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ironcalc_base::Model;
|
||||
/// # use ironcalc_base::cell::CellValue;
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||
/// model.merge_cells(0, "D4:F6");
|
||||
/// model.unmerge_cells(0, "D4:F6");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn unmerge_cells(&mut self, sheet: u32, range_ref: &str) -> Result<(), String> {
|
||||
let worksheet = self.workbook.worksheet(sheet)?;
|
||||
let merged_cells = worksheet.get_merged_cells_list();
|
||||
for (index, merge_node) in merged_cells.iter().enumerate() {
|
||||
let merge_block_range_ref = merge_node.get_merged_cells_str_ref()?;
|
||||
// finding the merge cell node to be deleted
|
||||
if merge_block_range_ref.as_str() == range_ref {
|
||||
// Merge cell to be deleted is found
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.merged_cells_list
|
||||
.remove(index);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err("Invalid merge_cell_ref, Merged cells to be deleted is not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
use super::CellReferenceIndex as CellReference;
|
||||
use crate::{test::util::new_empty_model, types::Cell};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
calc_result::Range,
|
||||
constants::{DEFAULT_WINDOW_HEIGH, DEFAULT_WINDOW_WIDTH},
|
||||
expressions::{
|
||||
lexer::LexerMode,
|
||||
parser::{
|
||||
@@ -15,7 +16,9 @@ use crate::{
|
||||
language::get_language,
|
||||
locale::get_locale,
|
||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||
types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||
types::{
|
||||
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
||||
},
|
||||
utils::ParsedReference,
|
||||
};
|
||||
|
||||
@@ -35,13 +38,26 @@ fn is_valid_sheet_name(name: &str) -> bool {
|
||||
|
||||
impl Model {
|
||||
/// 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) -> Worksheet {
|
||||
fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&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 {
|
||||
cols: vec![],
|
||||
rows: vec![],
|
||||
comments: vec![],
|
||||
dimension: "A1".to_string(),
|
||||
merge_cells: vec![],
|
||||
merged_cells_list: vec![],
|
||||
name: name.to_string(),
|
||||
shared_formulas: vec![],
|
||||
sheet_data: Default::default(),
|
||||
@@ -50,12 +66,8 @@ impl Model {
|
||||
color: Default::default(),
|
||||
frozen_columns: 0,
|
||||
frozen_rows: 0,
|
||||
selection: Selection {
|
||||
is_selected: false,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
},
|
||||
show_grid_lines: true,
|
||||
views,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +142,7 @@ impl Model {
|
||||
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) {
|
||||
self.parser
|
||||
.set_worksheets(self.workbook.get_worksheet_names());
|
||||
@@ -161,7 +173,8 @@ impl Model {
|
||||
let sheet_name = format!("{}{}", base_name, index);
|
||||
// Now we need a sheet_id
|
||||
let sheet_id = self.get_new_sheet_id();
|
||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids);
|
||||
self.workbook.worksheets.push(worksheet);
|
||||
self.reset_parsed_structures();
|
||||
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
||||
@@ -192,7 +205,8 @@ impl Model {
|
||||
Some(id) => id,
|
||||
None => self.get_new_sheet_id(),
|
||||
};
|
||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
|
||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids);
|
||||
if sheet_index as usize > self.workbook.worksheets.len() {
|
||||
return Err("Sheet index out of range".to_string());
|
||||
}
|
||||
@@ -339,11 +353,21 @@ impl Model {
|
||||
// "2020-08-06T21:20:53Z
|
||||
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
|
||||
let workbook = Workbook {
|
||||
shared_strings: vec![],
|
||||
defined_names: vec![],
|
||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
|
||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])],
|
||||
styles: Default::default(),
|
||||
name: name.to_string(),
|
||||
settings: WorkbookSettings {
|
||||
@@ -359,6 +383,7 @@ impl Model {
|
||||
last_modified: now,
|
||||
},
|
||||
tables: HashMap::new(),
|
||||
views,
|
||||
};
|
||||
let parsed_formulas = Vec::new();
|
||||
let worksheets = &workbook.worksheets;
|
||||
@@ -367,6 +392,7 @@ impl Model {
|
||||
let cells = HashMap::new();
|
||||
|
||||
// FIXME: Add support for display languages
|
||||
#[allow(clippy::expect_used)]
|
||||
let language = get_language("en").expect("").clone();
|
||||
|
||||
let mut model = Model {
|
||||
@@ -379,6 +405,7 @@ impl Model {
|
||||
locale,
|
||||
language,
|
||||
tz,
|
||||
view_id: 0,
|
||||
};
|
||||
model.parse_formulas();
|
||||
Ok(model)
|
||||
|
||||
@@ -161,26 +161,29 @@ impl Styles {
|
||||
|
||||
pub fn create_named_style(&mut self, style_name: &str, style: &Style) -> Result<(), String> {
|
||||
let style_index = self.create_new_style(style);
|
||||
self.add_named_cell_style(style_name, style_index)?;
|
||||
Ok(())
|
||||
self.add_named_cell_style(style_name, style_index)
|
||||
}
|
||||
|
||||
pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> i32 {
|
||||
let mut style = self.get_style(index);
|
||||
pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
|
||||
let mut style = self.get_style(index)?;
|
||||
style.quote_prefix = true;
|
||||
self.get_style_index_or_create(&style)
|
||||
Ok(self.get_style_index_or_create(&style))
|
||||
}
|
||||
|
||||
pub(crate) fn get_style_with_format(&mut self, index: i32, num_fmt: &str) -> i32 {
|
||||
let mut style = self.get_style(index);
|
||||
pub(crate) fn get_style_with_format(
|
||||
&mut self,
|
||||
index: i32,
|
||||
num_fmt: &str,
|
||||
) -> Result<i32, String> {
|
||||
let mut style = self.get_style(index)?;
|
||||
style.num_fmt = num_fmt.to_string();
|
||||
self.get_style_index_or_create(&style)
|
||||
Ok(self.get_style_index_or_create(&style))
|
||||
}
|
||||
|
||||
pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> i32 {
|
||||
let mut style = self.get_style(index);
|
||||
pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> Result<i32, String> {
|
||||
let mut style = self.get_style(index)?;
|
||||
style.quote_prefix = false;
|
||||
self.get_style_index_or_create(&style)
|
||||
Ok(self.get_style_index_or_create(&style))
|
||||
}
|
||||
|
||||
pub(crate) fn style_is_quote_prefix(&self, index: i32) -> bool {
|
||||
@@ -188,9 +191,11 @@ impl Styles {
|
||||
cell_xf.quote_prefix
|
||||
}
|
||||
|
||||
pub(crate) fn get_style(&self, index: i32) -> Style {
|
||||
let cell_xf = &self.cell_xfs[index as usize];
|
||||
|
||||
pub(crate) fn get_style(&self, index: i32) -> Result<Style, String> {
|
||||
let cell_xf = &self
|
||||
.cell_xfs
|
||||
.get(index as usize)
|
||||
.ok_or("Invalid index provided".to_string())?;
|
||||
let border_id = cell_xf.border_id as usize;
|
||||
let fill_id = cell_xf.fill_id as usize;
|
||||
let font_id = cell_xf.font_id as usize;
|
||||
@@ -198,14 +203,14 @@ impl Styles {
|
||||
let quote_prefix = cell_xf.quote_prefix;
|
||||
let alignment = cell_xf.alignment.clone();
|
||||
|
||||
Style {
|
||||
Ok(Style {
|
||||
alignment,
|
||||
num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
|
||||
fill: self.fills[fill_id].clone(),
|
||||
font: self.fonts[font_id].clone(),
|
||||
border: self.borders[border_id].clone(),
|
||||
quote_prefix,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,11 +223,18 @@ impl Model {
|
||||
column: i32,
|
||||
style: &Style,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
let style_index = self.workbook.styles.get_style_index_or_create(style);
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_cell_style(row, column, style_index);
|
||||
Ok(())
|
||||
.set_cell_style(row, column, style_index)
|
||||
}
|
||||
|
||||
pub fn copy_cell_style(
|
||||
@@ -237,9 +249,7 @@ impl Model {
|
||||
|
||||
self.workbook
|
||||
.worksheet_mut(destination_cell.0)?
|
||||
.set_cell_style(destination_cell.1, destination_cell.2, source_style_index);
|
||||
|
||||
Ok(())
|
||||
.set_cell_style(destination_cell.1, destination_cell.2, source_style_index)
|
||||
}
|
||||
|
||||
/// Sets the style "style_name" in cell
|
||||
@@ -250,11 +260,18 @@ impl Model {
|
||||
column: i32,
|
||||
style_name: &str,
|
||||
) -> Result<(), String> {
|
||||
// Checking first whether cell we are updating is part of Merged cells
|
||||
// if so returning with Err
|
||||
if self.is_part_of_merged_cells(sheet, row, column)? {
|
||||
return Err(format!(
|
||||
"Cell row : {}, col : {} is part of merged cells block, so singular update to the cell is not possible",
|
||||
row, column
|
||||
));
|
||||
}
|
||||
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
|
||||
self.workbook
|
||||
.worksheet_mut(sheet)?
|
||||
.set_cell_style(row, column, style_index);
|
||||
Ok(())
|
||||
.set_cell_style(row, column, style_index)
|
||||
}
|
||||
|
||||
pub fn set_sheet_style(&mut self, sheet: u32, style_name: &str) -> Result<(), String> {
|
||||
|
||||
@@ -76,10 +76,16 @@ fn fn_imconjugate() {
|
||||
fn fn_imcos() {
|
||||
let mut model = new_empty_model();
|
||||
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();
|
||||
|
||||
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
|
||||
assert_eq!(model._get_text("A3"), "TRUE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,6 +15,7 @@ mod test_fn_concatenate;
|
||||
mod test_fn_count;
|
||||
mod test_fn_exact;
|
||||
mod test_fn_financial;
|
||||
mod test_fn_formulatext;
|
||||
mod test_fn_if;
|
||||
mod test_fn_maxifs;
|
||||
mod test_fn_minifs;
|
||||
@@ -24,6 +25,7 @@ mod test_fn_sum;
|
||||
mod test_fn_sumifs;
|
||||
mod test_fn_textbefore;
|
||||
mod test_fn_textjoin;
|
||||
mod test_fn_unicode;
|
||||
mod test_forward_references;
|
||||
mod test_frozen_rows_columns;
|
||||
mod test_general;
|
||||
@@ -51,7 +53,9 @@ mod test_extend;
|
||||
mod test_fn_type;
|
||||
mod test_frozen_rows_and_columns;
|
||||
mod test_get_cell_content;
|
||||
mod test_model_merge_cell_fns;
|
||||
mod test_percentage;
|
||||
mod test_set_functions_error_handling;
|
||||
mod test_today;
|
||||
mod test_types;
|
||||
mod user_model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::constants::LAST_COLUMN;
|
||||
use crate::constants::{DEFAULT_ROW_HEIGHT, LAST_COLUMN};
|
||||
use crate::model::Model;
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::types::Col;
|
||||
@@ -87,7 +87,8 @@ fn test_insert_rows_styles() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
assert!(
|
||||
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
||||
< f64::EPSILON
|
||||
);
|
||||
// sets height 42 in row 10
|
||||
model
|
||||
@@ -106,7 +107,8 @@ fn test_insert_rows_styles() {
|
||||
|
||||
// Row 10 has the default height
|
||||
assert!(
|
||||
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
||||
< f64::EPSILON
|
||||
);
|
||||
|
||||
// Row 10 is now row 15
|
||||
@@ -120,7 +122,8 @@ fn test_delete_rows_styles() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
assert!(
|
||||
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
||||
< f64::EPSILON
|
||||
);
|
||||
// sets height 42 in row 10
|
||||
model
|
||||
@@ -139,7 +142,8 @@ fn test_delete_rows_styles() {
|
||||
|
||||
// Row 10 has the default height
|
||||
assert!(
|
||||
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
|
||||
(DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
|
||||
< f64::EPSILON
|
||||
);
|
||||
|
||||
// Row 10 is now row 5
|
||||
|
||||
@@ -26,7 +26,7 @@ fn test_column_width() {
|
||||
assert!((worksheet.get_column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||
assert!((worksheet.get_column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||
assert!((worksheet.get_column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||
assert_eq!(model.get_cell_style_index(0, 23, 2), 6);
|
||||
assert_eq!(model.get_cell_style_index(0, 23, 2), Ok(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -53,7 +53,7 @@ fn test_column_width_lower_edge() {
|
||||
assert!(
|
||||
(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]
|
||||
@@ -80,5 +80,5 @@ fn test_column_width_higher_edge() {
|
||||
);
|
||||
assert!((worksheet.get_column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||
assert!((worksheet.get_column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||
assert_eq!(model.get_cell_style_index(0, 23, 16), 1);
|
||||
assert_eq!(model.get_cell_style_index(0, 23, 16), Ok(1));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::panic)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
use crate::types::Cell;
|
||||
|
||||
47
base/src/test/test_fn_formulatext.rs
Normal file
47
base/src/test/test_fn_formulatext.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn simple_cases() {}
|
||||
|
||||
#[test]
|
||||
fn wrong_number_of_arguments() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=FORMULATEXT()");
|
||||
model._set("A2", "=FORMULATEXT(\"B\",\"A\")");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
assert_eq!(model._get_text("A2"), *"#ERROR!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_sheet_ref() {
|
||||
let mut model = new_empty_model();
|
||||
model.new_sheet();
|
||||
model._set("A1", "=FORMULATEXT(Sheet1!A1:Sheet2!A1)");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_intersection() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=FORMULATEXT(C1:C2)");
|
||||
model._set("A2", "=FORMULATEXT(D1:E1)");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
assert_eq!(model._get_text("A2"), *"#ERROR!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_reference() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=FORMULATEXT(42)");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
}
|
||||
63
base/src/test/test_fn_unicode.rs
Normal file
63
base/src/test/test_fn_unicode.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn simple_cases() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=UNICODE(\"1,00\")");
|
||||
model._set("A2", "=UNICODE(\"1\")");
|
||||
model._set("A3", "=UNICODE(1)");
|
||||
model._set("A4", "=UNICODE(\"T\")");
|
||||
model._set("A5", "=UNICODE(\"TRUE\")");
|
||||
model._set("A6", "=UNICODE(TRUE)");
|
||||
model._set("A7", "=UNICODE(FALSE)");
|
||||
model._set("A8", "=UNICODE(\"の\")");
|
||||
model._set("A9", "=UNICODE(\" \")");
|
||||
|
||||
model._set("A10", "=_xlfn.UNICODE(\"T\")");
|
||||
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"49");
|
||||
assert_eq!(model._get_text("A2"), *"49");
|
||||
assert_eq!(model._get_text("A3"), *"49");
|
||||
assert_eq!(model._get_text("A4"), *"84");
|
||||
assert_eq!(model._get_text("A5"), *"84");
|
||||
assert_eq!(model._get_text("A6"), *"84");
|
||||
assert_eq!(model._get_text("A7"), *"70");
|
||||
assert_eq!(model._get_text("A8"), *"12398");
|
||||
assert_eq!(model._get_text("A9"), *"32");
|
||||
assert_eq!(model._get_text("A10"), *"84");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_cases() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=UNICODE(\"\")");
|
||||
model._set("A2", "=UNICODE(#CALC!)");
|
||||
model._set("A3", "=UNICODE(#NAME?)");
|
||||
model._set("A4", "=UNICODE(#VALUE!)");
|
||||
model._set("A5", "=UNICODE(#REF!)");
|
||||
model._set("A6", "=UNICODE(#DIV/0!)");
|
||||
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#VALUE!");
|
||||
assert_eq!(model._get_text("A2"), *"#CALC!");
|
||||
assert_eq!(model._get_text("A3"), *"#NAME?");
|
||||
assert_eq!(model._get_text("A4"), *"#VALUE!");
|
||||
assert_eq!(model._get_text("A5"), *"#REF!");
|
||||
assert_eq!(model._get_text("A6"), *"#DIV/0!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_number_of_arguments() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=UNICODE()");
|
||||
model._set("A2", "=UNICODE(\"B\",\"A\")");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A1"), *"#ERROR!");
|
||||
assert_eq!(model._get_text("A2"), *"#ERROR!");
|
||||
}
|
||||
@@ -17,7 +17,9 @@ fn test_empty_model() {
|
||||
#[test]
|
||||
fn test_model_simple_evaluation() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "= 1 + 3".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "= 1 + 3".to_string())
|
||||
.unwrap();
|
||||
model.evaluate();
|
||||
let result = model._get_text_at(0, 1, 1);
|
||||
assert_eq!(result, *"4");
|
||||
@@ -43,7 +45,7 @@ fn test_model_simple_evaluation_order() {
|
||||
#[test]
|
||||
fn test_model_invalid_formula() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "= 1 +".to_string());
|
||||
model.set_user_input(0, 1, 1, "= 1 +".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
let result = model._get_text_at(0, 1, 1);
|
||||
assert_eq!(result, *"#ERROR!");
|
||||
@@ -54,8 +56,10 @@ fn test_model_invalid_formula() {
|
||||
#[test]
|
||||
fn test_model_dependencies() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "23".to_string()); // A1
|
||||
model.set_user_input(0, 1, 2, "= A1* 2-4".to_string()); // B1
|
||||
model.set_user_input(0, 1, 1, "23".to_string()).unwrap(); // A1
|
||||
model
|
||||
.set_user_input(0, 1, 2, "= A1* 2-4".to_string())
|
||||
.unwrap(); // B1
|
||||
model.evaluate();
|
||||
let result = model._get_text_at(0, 1, 1);
|
||||
assert_eq!(result, *"23");
|
||||
@@ -65,7 +69,9 @@ fn test_model_dependencies() {
|
||||
let result = model._get_formula("B1");
|
||||
assert_eq!(result, *"=A1*2-4");
|
||||
|
||||
model.set_user_input(0, 2, 1, "=SUM(A1, B1)".to_string()); // A2
|
||||
model
|
||||
.set_user_input(0, 2, 1, "=SUM(A1, B1)".to_string())
|
||||
.unwrap(); // A2
|
||||
model.evaluate();
|
||||
let result = model._get_text_at(0, 2, 1);
|
||||
assert_eq!(result, *"65");
|
||||
@@ -74,8 +80,10 @@ fn test_model_dependencies() {
|
||||
#[test]
|
||||
fn test_model_strings() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "Hello World".to_string());
|
||||
model.set_user_input(0, 1, 2, "=A1".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "Hello World".to_string())
|
||||
.unwrap();
|
||||
model.set_user_input(0, 1, 2, "=A1".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
let result = model._get_text_at(0, 1, 1);
|
||||
assert_eq!(result, *"Hello World");
|
||||
@@ -152,21 +160,35 @@ fn test_to_excel_precision_str() {
|
||||
#[test]
|
||||
fn test_booleans() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "true".to_string());
|
||||
model.set_user_input(0, 2, 1, "TRUE".to_string());
|
||||
model.set_user_input(0, 3, 1, "True".to_string());
|
||||
model.set_user_input(0, 4, 1, "false".to_string());
|
||||
model.set_user_input(0, 5, 1, "FALSE".to_string());
|
||||
model.set_user_input(0, 6, 1, "False".to_string());
|
||||
model.set_user_input(0, 1, 1, "true".to_string()).unwrap();
|
||||
model.set_user_input(0, 2, 1, "TRUE".to_string()).unwrap();
|
||||
model.set_user_input(0, 3, 1, "True".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 1, "false".to_string()).unwrap();
|
||||
model.set_user_input(0, 5, 1, "FALSE".to_string()).unwrap();
|
||||
model.set_user_input(0, 6, 1, "False".to_string()).unwrap();
|
||||
|
||||
model.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string());
|
||||
model.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string());
|
||||
model.set_user_input(0, 3, 2, "=ISLOGICAL(A3)".to_string());
|
||||
model.set_user_input(0, 4, 2, "=ISLOGICAL(A4)".to_string());
|
||||
model.set_user_input(0, 5, 2, "=ISLOGICAL(A5)".to_string());
|
||||
model.set_user_input(0, 6, 2, "=ISLOGICAL(A6)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string())
|
||||
.unwrap();
|
||||
model
|
||||
.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string())
|
||||
.unwrap();
|
||||
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.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -191,19 +213,19 @@ fn test_booleans() {
|
||||
#[test]
|
||||
fn test_set_cell_style() {
|
||||
let mut model = new_empty_model();
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
|
||||
style.font.b = true;
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(style.font.b);
|
||||
|
||||
style.font.b = false;
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
|
||||
let style = model.get_style_for_cell(0, 1, 1);
|
||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
}
|
||||
|
||||
@@ -211,21 +233,21 @@ fn test_set_cell_style() {
|
||||
fn test_copy_cell_style() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
style.font.b = true;
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 2);
|
||||
let mut style = model.get_style_for_cell(0, 1, 2).unwrap();
|
||||
style.font.i = true;
|
||||
assert!(model.set_cell_style(0, 1, 2, &style).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);
|
||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(style.font.b);
|
||||
assert!(!style.font.i);
|
||||
|
||||
let style = model.get_style_for_cell(0, 1, 2);
|
||||
let style = model.get_style_for_cell(0, 1, 2).unwrap();
|
||||
assert!(style.font.b);
|
||||
assert!(!style.font.i);
|
||||
}
|
||||
@@ -234,15 +256,15 @@ fn test_copy_cell_style() {
|
||||
fn test_get_cell_style_index() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
assert_eq!(style_index, 0);
|
||||
assert!(!style.font.b);
|
||||
|
||||
style.font.b = true;
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
|
||||
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
assert_eq!(style_index, 1);
|
||||
}
|
||||
|
||||
@@ -250,29 +272,29 @@ fn test_get_cell_style_index() {
|
||||
fn test_model_set_cells_with_values_styles() {
|
||||
let mut model = new_empty_model();
|
||||
// Inputs
|
||||
model.set_user_input(0, 1, 1, "21".to_string()); // A1
|
||||
model.set_user_input(0, 2, 1, "2".to_string()); // A2
|
||||
model.set_user_input(0, 1, 1, "21".to_string()).unwrap(); // A1
|
||||
model.set_user_input(0, 2, 1, "2".to_string()).unwrap(); // A2
|
||||
|
||||
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
assert_eq!(style_index, 0);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
style.font.b = true;
|
||||
assert!(model.set_cell_style(0, 1, 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);
|
||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
assert_eq!(style_index, 1);
|
||||
let style_index = model.get_cell_style_index(0, 2, 1);
|
||||
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
||||
assert_eq!(style_index, 1);
|
||||
|
||||
model.update_cell_with_number(0, 1, 2, 1.0);
|
||||
model.update_cell_with_number(0, 2, 1, 2.0);
|
||||
model.update_cell_with_number(0, 1, 2, 1.0).unwrap();
|
||||
model.update_cell_with_number(0, 2, 1, 2.0).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
// Styles are not modified
|
||||
let style_index = model.get_cell_style_index(0, 1, 1);
|
||||
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
assert_eq!(style_index, 1);
|
||||
let style_index = model.get_cell_style_index(0, 2, 1);
|
||||
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
||||
assert_eq!(style_index, 1);
|
||||
}
|
||||
|
||||
@@ -280,20 +302,20 @@ fn test_model_set_cells_with_values_styles() {
|
||||
fn test_style_fmt_id() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
style.num_fmt = "#.##".to_string();
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
let style = model.get_style_for_cell(0, 1, 1);
|
||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert_eq!(style.num_fmt, "#.##");
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 10, 1);
|
||||
let mut style = model.get_style_for_cell(0, 10, 1).unwrap();
|
||||
style.num_fmt = "$$#,##0.0000".to_string();
|
||||
assert!(model.set_cell_style(0, 10, 1, &style).is_ok());
|
||||
let style = model.get_style_for_cell(0, 10, 1);
|
||||
let style = model.get_style_for_cell(0, 10, 1).unwrap();
|
||||
assert_eq!(style.num_fmt, "$$#,##0.0000");
|
||||
|
||||
// Make sure old style is not touched
|
||||
let style = model.get_style_for_cell(0, 1, 1);
|
||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert_eq!(style.num_fmt, "#.##");
|
||||
}
|
||||
|
||||
@@ -357,9 +379,13 @@ fn set_input_autocomplete() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "1");
|
||||
model._set("A2", "2");
|
||||
model.set_user_input(0, 3, 1, "=SUM(A1:A2".to_string());
|
||||
model
|
||||
.set_user_input(0, 3, 1, "=SUM(A1:A2".to_string())
|
||||
.unwrap();
|
||||
// This will fail anyway
|
||||
model.set_user_input(0, 4, 1, "=SUM(A1*".to_string());
|
||||
model
|
||||
.set_user_input(0, 4, 1, "=SUM(A1*".to_string())
|
||||
.unwrap();
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_formula("A3"), "=SUM(A1:A2)");
|
||||
@@ -405,7 +431,7 @@ fn test_get_formatted_cell_value() {
|
||||
model._set("A5", "123.456");
|
||||
|
||||
// change A5 format
|
||||
let mut style = model.get_style_for_cell(0, 5, 1);
|
||||
let mut style = model.get_style_for_cell(0, 5, 1).unwrap();
|
||||
style.num_fmt = "$#,##0.00".to_string();
|
||||
model.set_cell_style(0, 5, 1, &style).unwrap();
|
||||
|
||||
|
||||
@@ -5,8 +5,12 @@ use crate::test::util::new_empty_model;
|
||||
#[test]
|
||||
fn test_formulas() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "$100.348".to_string());
|
||||
model.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "$100.348".to_string())
|
||||
.unwrap();
|
||||
model
|
||||
.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::test::util::new_empty_model;
|
||||
|
||||
#[test]
|
||||
fn test_metadata_new_model() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "5.5".to_string());
|
||||
model.set_user_input(0, 1, 1, "5.5".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
let metadata = &model.workbook.metadata;
|
||||
assert_eq!(metadata.application, "IronCalc Sheets");
|
||||
|
||||
@@ -14,7 +14,9 @@ fn test_is_empty_cell_non_existing_sheet() {
|
||||
fn test_is_empty_cell() {
|
||||
let mut model = new_empty_model();
|
||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
||||
model.set_user_input(0, 3, 1, "Hello World".to_string());
|
||||
model
|
||||
.set_user_input(0, 3, 1, "Hello World".to_string())
|
||||
.unwrap();
|
||||
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
|
||||
model.cell_clear_contents(0, 3, 1).unwrap();
|
||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
||||
|
||||
155
base/src/test/test_model_merge_cell_fns.rs
Normal file
155
base/src/test/test_model_merge_cell_fns.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{test::util::new_empty_model, types::CellType};
|
||||
|
||||
#[test]
|
||||
fn test_model_set_fns_related_to_merge_cells() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
// creating a merge cell of D1:F2
|
||||
model.merge_cells(0, "D1:F2").unwrap();
|
||||
|
||||
// Updating the mother cell of Merge cells and expecting the update to go through
|
||||
model.set_user_input(0, 1, 4, "Hello".to_string()).unwrap();
|
||||
assert_eq!(model.get_cell_content(0, 1, 4).unwrap(), "Hello");
|
||||
assert_eq!(model.get_cell_type(0, 1, 4).unwrap(), CellType::Text);
|
||||
|
||||
// Updating cell which is not in Merge cell block
|
||||
assert_eq!(model.set_user_input(0, 1, 3, "Hello".to_string()), Ok(()));
|
||||
assert_eq!(model.get_cell_content(0, 1, 3), Ok("Hello".to_string()));
|
||||
assert_eq!(model.get_cell_type(0, 1, 3), Ok(CellType::Text));
|
||||
|
||||
// 1: testing with set_user_input()
|
||||
assert_eq!(
|
||||
model
|
||||
.set_user_input(0, 1, 5, "Hello".to_string()),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_cell_content(0, 1, 5), Ok("".to_string()));
|
||||
assert_eq!(model.get_cell_type(0, 1, 5), Ok(CellType::Number));
|
||||
|
||||
// 2: testing with update_cell_with_bool()
|
||||
assert_eq!(
|
||||
model
|
||||
.update_cell_with_bool(0, 1, 5, true),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_cell_content(0, 1, 5), Ok("".to_string()));
|
||||
assert_eq!(model.get_cell_type(0, 1, 5), Ok(CellType::Number));
|
||||
|
||||
// 3: testing with update_cell_with_formula()
|
||||
assert_eq!(
|
||||
model
|
||||
.update_cell_with_formula(0, 1, 5, "=SUM(A1+A2)".to_string()),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_cell_type(0, 1, 5), Ok(CellType::Number));
|
||||
|
||||
// 4: testing with update_cell_with_number()
|
||||
assert_eq!(
|
||||
model
|
||||
.update_cell_with_number(0, 1, 5, 10.0),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_cell_content(0, 1, 5), Ok("".to_string()));
|
||||
assert_eq!(model.get_cell_type(0, 1, 5), Ok(CellType::Number));
|
||||
|
||||
// 5: testing with update_cell_with_text()
|
||||
assert_eq!(
|
||||
model
|
||||
.update_cell_with_text(0, 1, 5, "new text"),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_cell_content(0, 1, 5), Ok("".to_string()));
|
||||
assert_eq!(model.get_cell_type(0, 1, 5), Ok(CellType::Number));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_merge_cells_crud_api() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
// creating a merge cell of D4:F6
|
||||
model.merge_cells(0, "D4:F6").unwrap();
|
||||
model
|
||||
.set_user_input(0, 4, 4, "Merge Block".to_string())
|
||||
.unwrap();
|
||||
// CRUD APIS testing on Merge Cells
|
||||
|
||||
// Case1: Creating a new merge cell without overlapping
|
||||
// Newly created Merge block is left to D4:F6
|
||||
assert_eq!(model.merge_cells(0, "A1:B4"), Ok(()));
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().merged_cells_list.len(),
|
||||
2
|
||||
);
|
||||
model.set_user_input(0, 1, 1, "left".to_string()).unwrap();
|
||||
|
||||
// Newly created Merge block is right to D4:F6
|
||||
assert_eq!(model.merge_cells(0, "G1:H7"), Ok(()));
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().merged_cells_list.len(),
|
||||
3
|
||||
);
|
||||
model.set_user_input(0, 1, 7, "right".to_string()).unwrap();
|
||||
|
||||
// Newly created Merge block is above to D4:F6
|
||||
assert_eq!(model.merge_cells(0, "C1:D3"), Ok(()));
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().merged_cells_list.len(),
|
||||
4
|
||||
);
|
||||
model.set_user_input(0, 1, 3, "top".to_string()).unwrap();
|
||||
|
||||
// Newly created Merge block is down to D4:F6
|
||||
assert_eq!(model.merge_cells(0, "D8:E9"), Ok(()));
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().merged_cells_list.len(),
|
||||
5
|
||||
);
|
||||
model.set_user_input(0, 8, 4, "down".to_string()).unwrap();
|
||||
|
||||
// Case2: Creating a new merge cell with overlapping with other 3 merged cell
|
||||
assert_eq!(model.merge_cells(0, "C1:G4"), Ok(()));
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().merged_cells_list.len(),
|
||||
3
|
||||
);
|
||||
model
|
||||
.set_user_input(0, 1, 3, "overlapped_new_merge_block".to_string())
|
||||
.unwrap();
|
||||
|
||||
// Case3: Giving wrong parsing range
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "C3:A1"),
|
||||
Err("Invalid parse range. Merge Mother cell always be top left cell".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "CA:A1"),
|
||||
Err("Invalid range: 'CA:A1'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "C0:A1"),
|
||||
Err("Invalid range: 'C0:A1'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "C1:A0"),
|
||||
Err("Invalid range: 'C1:A0'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "C1"),
|
||||
Err("Invalid range: 'C1'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.merge_cells(0, "C1:A1:B1"),
|
||||
Err("Invalid range: 'C1:A1:B1'".to_string())
|
||||
);
|
||||
|
||||
// Case3: Giving wrong merge_ref, which would resulting in error (Merge cell to be deleted is not found)
|
||||
assert_eq!(
|
||||
model.unmerge_cells(0, "C1:E1"),
|
||||
Err("Invalid merge_cell_ref, Merged cells to be deleted is not found".to_string())
|
||||
);
|
||||
|
||||
// Case4: unmerge scenario
|
||||
assert_eq!(model.unmerge_cells(0, "C1:G4"), Ok(()));
|
||||
}
|
||||
@@ -57,12 +57,12 @@ fn test_quote_prefix_enter() {
|
||||
model._set("A2", "=ISTEXT(A1)");
|
||||
model.evaluate();
|
||||
// We introduce a value with a "quote prefix" index
|
||||
model.set_user_input(0, 1, 3, "'=A1".to_string());
|
||||
model.set_user_input(0, 1, 3, "'=A1".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("C1"), *"=A1");
|
||||
|
||||
// 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());
|
||||
model.set_user_input(0, 1, 4, "=A1".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("D1"), *"123");
|
||||
}
|
||||
@@ -75,7 +75,7 @@ fn test_quote_prefix_reenter() {
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
// We introduce a value with a "quote prefix" index
|
||||
model.set_user_input(0, 1, 1, "123".to_string());
|
||||
model.set_user_input(0, 1, 1, "123".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||
}
|
||||
@@ -83,7 +83,7 @@ fn test_quote_prefix_reenter() {
|
||||
#[test]
|
||||
fn test_update_cell_quote() {
|
||||
let mut model = new_empty_model();
|
||||
model.update_cell_with_text(0, 1, 1, "= 1 + 3");
|
||||
model.update_cell_with_text(0, 1, 1, "= 1 + 3").unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), *"= 1 + 3");
|
||||
assert!(!model._has_formula("A1"));
|
||||
@@ -92,12 +92,12 @@ fn test_update_cell_quote() {
|
||||
#[test]
|
||||
fn test_update_quote_prefix_reenter() {
|
||||
let mut model = new_empty_model();
|
||||
model.update_cell_with_text(0, 1, 1, "123");
|
||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
||||
model._set("A2", "=ISTEXT(A1)");
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
// We reenter as a number
|
||||
model.update_cell_with_number(0, 1, 1, 123.0);
|
||||
model.update_cell_with_number(0, 1, 1, 123.0).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||
}
|
||||
@@ -105,12 +105,12 @@ fn test_update_quote_prefix_reenter() {
|
||||
#[test]
|
||||
fn test_update_quote_prefix_reenter_bool() {
|
||||
let mut model = new_empty_model();
|
||||
model.update_cell_with_text(0, 1, 1, "TRUE");
|
||||
model.update_cell_with_text(0, 1, 1, "TRUE").unwrap();
|
||||
model._set("A2", "=ISTEXT(A1)");
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
// We enter a bool
|
||||
model.update_cell_with_bool(0, 1, 1, true);
|
||||
model.update_cell_with_bool(0, 1, 1, true).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"FALSE");
|
||||
}
|
||||
@@ -118,29 +118,29 @@ fn test_update_quote_prefix_reenter_bool() {
|
||||
#[test]
|
||||
fn test_update_quote_prefix_reenter_text() {
|
||||
let mut model = new_empty_model();
|
||||
model.update_cell_with_text(0, 1, 1, "123");
|
||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
||||
model._set("A2", "=ISTEXT(A1)");
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
||||
// We enter a string
|
||||
model.update_cell_with_text(0, 1, 1, "Hello");
|
||||
model.update_cell_with_text(0, 1, 1, "Hello").unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
assert!(!model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||
assert!(!model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_quote_prefix_reenter_text_2() {
|
||||
let mut model = new_empty_model();
|
||||
model.update_cell_with_text(0, 1, 1, "123");
|
||||
model.update_cell_with_text(0, 1, 1, "123").unwrap();
|
||||
model._set("A2", "=ISTEXT(A1)");
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
||||
// We enter another number
|
||||
model.update_cell_with_text(0, 1, 1, "42");
|
||||
model.update_cell_with_text(0, 1, 1, "42").unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A2"), *"TRUE");
|
||||
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
|
||||
assert!(model.get_style_for_cell(0, 1, 1).unwrap().quote_prefix);
|
||||
}
|
||||
|
||||
451
base/src/test/test_set_functions_error_handling.rs
Normal file
451
base/src/test/test_set_functions_error_handling.rs
Normal file
@@ -0,0 +1,451 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
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,16 +5,28 @@ use crate::{cell::CellValue, test::util::new_empty_model};
|
||||
#[test]
|
||||
fn test_currencies() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "$100.348".to_string());
|
||||
model.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "$100.348".to_string())
|
||||
.unwrap();
|
||||
model
|
||||
.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.set_user_input(0, 2, 1, "$ 100.348".to_string());
|
||||
model.set_user_input(0, 2, 2, "=ISNUMBER(A2)".to_string());
|
||||
model
|
||||
.set_user_input(0, 2, 1, "$ 100.348".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());
|
||||
model.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string());
|
||||
model.set_user_input(0, 3, 1, "100$".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.set_user_input(0, 4, 1, "3.1415926$".to_string());
|
||||
model
|
||||
.set_user_input(0, 4, 1, "3.1415926$".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -43,9 +55,9 @@ fn test_currencies() {
|
||||
#[test]
|
||||
fn scientific() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "3e-4".to_string());
|
||||
model.set_user_input(0, 2, 1, "5e-4$".to_string());
|
||||
model.set_user_input(0, 3, 1, "6e-4%".to_string());
|
||||
model.set_user_input(0, 1, 1, "3e-4".to_string()).unwrap();
|
||||
model.set_user_input(0, 2, 1, "5e-4$".to_string()).unwrap();
|
||||
model.set_user_input(0, 3, 1, "6e-4%".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -61,9 +73,13 @@ fn scientific() {
|
||||
#[test]
|
||||
fn test_percentage() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 10, 1, "50%".to_string());
|
||||
model.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string());
|
||||
model.set_user_input(0, 11, 1, "55.759%".to_string());
|
||||
model.set_user_input(0, 10, 1, "50%".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string())
|
||||
.unwrap();
|
||||
model
|
||||
.set_user_input(0, 11, 1, "55.759%".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -81,8 +97,8 @@ fn test_percentage_ops() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "5%");
|
||||
model._set("A2", "20%");
|
||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
|
||||
model.set_user_input(0, 4, 1, "=A1*A2".to_string());
|
||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 1, "=A1*A2".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -93,11 +109,19 @@ fn test_percentage_ops() {
|
||||
#[test]
|
||||
fn test_numbers() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "1,000,000".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "1,000,000".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.set_user_input(0, 20, 1, "50,123.549".to_string());
|
||||
model.set_user_input(0, 21, 1, "50,12.549".to_string());
|
||||
model.set_user_input(0, 22, 1, "1,234567".to_string());
|
||||
model
|
||||
.set_user_input(0, 20, 1, "50,123.549".to_string())
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
@@ -131,7 +155,7 @@ fn test_numbers() {
|
||||
#[test]
|
||||
fn test_negative_numbers() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "-100".to_string());
|
||||
model.set_user_input(0, 1, 1, "-100".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -144,15 +168,19 @@ fn test_negative_numbers() {
|
||||
#[test]
|
||||
fn test_negative_currencies() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "-$100".to_string());
|
||||
model.set_user_input(0, 2, 1, "-$99.123".to_string());
|
||||
model.set_user_input(0, 1, 1, "-$100".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 2, 1, "-$99.123".to_string())
|
||||
.unwrap();
|
||||
// This is valid!
|
||||
model.set_user_input(0, 3, 1, "$-345".to_string());
|
||||
model.set_user_input(0, 3, 1, "$-345".to_string()).unwrap();
|
||||
|
||||
model.set_user_input(0, 1, 2, "-200$".to_string());
|
||||
model.set_user_input(0, 2, 2, "-92.689$".to_string());
|
||||
model.set_user_input(0, 1, 2, "-200$".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 2, 2, "-92.689$".to_string())
|
||||
.unwrap();
|
||||
// This is valid!
|
||||
model.set_user_input(0, 3, 2, "-22$".to_string());
|
||||
model.set_user_input(0, 3, 2, "-22$".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -174,8 +202,10 @@ fn test_formulas() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "$100");
|
||||
model._set("A2", "$200");
|
||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
|
||||
model.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string());
|
||||
model.set_user_input(0, 3, 1, "=A1+A2".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -198,9 +228,9 @@ fn test_product() {
|
||||
model._set("A2", "$5");
|
||||
model._set("A3", "4");
|
||||
|
||||
model.set_user_input(0, 1, 2, "=A1*A2".to_string());
|
||||
model.set_user_input(0, 2, 2, "=A1*A3".to_string());
|
||||
model.set_user_input(0, 3, 2, "=A1*3".to_string());
|
||||
model.set_user_input(0, 1, 2, "=A1*A2".to_string()).unwrap();
|
||||
model.set_user_input(0, 2, 2, "=A1*A3".to_string()).unwrap();
|
||||
model.set_user_input(0, 3, 2, "=A1*3".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -216,10 +246,12 @@ fn test_division() {
|
||||
model._set("A2", "$5");
|
||||
model._set("A3", "4");
|
||||
|
||||
model.set_user_input(0, 1, 2, "=A1/A2".to_string());
|
||||
model.set_user_input(0, 2, 2, "=A1/A3".to_string());
|
||||
model.set_user_input(0, 3, 2, "=A1/2".to_string());
|
||||
model.set_user_input(0, 4, 2, "=100/A2".to_string());
|
||||
model.set_user_input(0, 1, 2, "=A1/A2".to_string()).unwrap();
|
||||
model.set_user_input(0, 2, 2, "=A1/A3".to_string()).unwrap();
|
||||
model.set_user_input(0, 3, 2, "=A1/2".to_string()).unwrap();
|
||||
model
|
||||
.set_user_input(0, 4, 2, "=100/A2".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -235,54 +267,54 @@ fn test_some_complex_examples() {
|
||||
// $3.00 / 2 = $1.50
|
||||
model._set("A1", "$3.00");
|
||||
model._set("A2", "2");
|
||||
model.set_user_input(0, 3, 1, "=A1/A2".to_string());
|
||||
model.set_user_input(0, 3, 1, "=A1/A2".to_string()).unwrap();
|
||||
|
||||
// $3 / 2 = $1
|
||||
model._set("B1", "$3");
|
||||
model._set("B2", "2");
|
||||
model.set_user_input(0, 3, 2, "=B1/B2".to_string());
|
||||
model.set_user_input(0, 3, 2, "=B1/B2".to_string()).unwrap();
|
||||
|
||||
// $5.00 * 25% = 25% * $5.00 = $1.25
|
||||
model._set("C1", "$5.00");
|
||||
model._set("C2", "25%");
|
||||
model.set_user_input(0, 3, 3, "=C1*C2".to_string());
|
||||
model.set_user_input(0, 4, 3, "=C2*C1".to_string());
|
||||
model.set_user_input(0, 3, 3, "=C1*C2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 3, "=C2*C1".to_string()).unwrap();
|
||||
|
||||
// $5 * 75% = 75% * $5 = $1
|
||||
model._set("D1", "$5");
|
||||
model._set("D2", "75%");
|
||||
model.set_user_input(0, 3, 4, "=D1*D2".to_string());
|
||||
model.set_user_input(0, 4, 4, "=D2*D1".to_string());
|
||||
model.set_user_input(0, 3, 4, "=D1*D2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 4, "=D2*D1".to_string()).unwrap();
|
||||
|
||||
// $10 + $9.99 = $9.99 + $10 = $19.99
|
||||
model._set("E1", "$10");
|
||||
model._set("E2", "$9.99");
|
||||
model.set_user_input(0, 3, 5, "=E1+E2".to_string());
|
||||
model.set_user_input(0, 4, 5, "=E2+E1".to_string());
|
||||
model.set_user_input(0, 3, 5, "=E1+E2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 5, "=E2+E1".to_string()).unwrap();
|
||||
|
||||
// $2 * 2 = 2 * $2 = $4
|
||||
model._set("F1", "$2");
|
||||
model._set("F2", "2");
|
||||
model.set_user_input(0, 3, 6, "=F1*F2".to_string());
|
||||
model.set_user_input(0, 4, 6, "=F2*F1".to_string());
|
||||
model.set_user_input(0, 3, 6, "=F1*F2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 6, "=F2*F1".to_string()).unwrap();
|
||||
|
||||
// $2.50 * 2 = 2 * $2.50 = $5.00
|
||||
model._set("G1", "$2.50");
|
||||
model._set("G2", "2");
|
||||
model.set_user_input(0, 3, 7, "=G1*G2".to_string());
|
||||
model.set_user_input(0, 4, 7, "=G2*G1".to_string());
|
||||
model.set_user_input(0, 3, 7, "=G1*G2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 7, "=G2*G1".to_string()).unwrap();
|
||||
|
||||
// $2 * 2.5 = 2.5 * $2 = $5
|
||||
model._set("H1", "$2");
|
||||
model._set("H2", "2.5");
|
||||
model.set_user_input(0, 3, 8, "=H1*H2".to_string());
|
||||
model.set_user_input(0, 4, 8, "=H2*H1".to_string());
|
||||
model.set_user_input(0, 3, 8, "=H1*H2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 8, "=H2*H1".to_string()).unwrap();
|
||||
|
||||
// 10% * 1,000 = 1,000 * 10% = 100
|
||||
model._set("I1", "10%");
|
||||
model._set("I2", "1,000");
|
||||
model.set_user_input(0, 3, 9, "=I1*I2".to_string());
|
||||
model.set_user_input(0, 4, 9, "=I2*I1".to_string());
|
||||
model.set_user_input(0, 3, 9, "=I1*I2".to_string()).unwrap();
|
||||
model.set_user_input(0, 4, 9, "=I2*I1".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -320,9 +352,15 @@ fn test_financial_functions() {
|
||||
model._set("A3", "10");
|
||||
model._set("A4", "$10,000");
|
||||
|
||||
model.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());
|
||||
model.set_user_input(0, 7, 1, "=PMT(0.2, 3, -200)".to_string());
|
||||
model
|
||||
.set_user_input(0, 5, 1, "=PMT(A2/12,A3,A4)".to_string())
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
@@ -339,9 +377,15 @@ fn test_sum_function() {
|
||||
model._set("A1", "$100");
|
||||
model._set("A2", "$300");
|
||||
|
||||
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
|
||||
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 2, "=SUM(A:A)".to_string())
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
@@ -353,7 +397,7 @@ fn test_sum_function() {
|
||||
#[test]
|
||||
fn test_number() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "3".to_string());
|
||||
model.set_user_input(0, 1, 1, "3".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -367,7 +411,9 @@ fn test_number() {
|
||||
#[test]
|
||||
fn test_currencies_eur_prefix() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "€100.348".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "€100.348".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -381,19 +427,27 @@ fn test_currencies_eur_prefix() {
|
||||
#[test]
|
||||
fn test_currencies_eur_suffix() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "100.348€".to_string());
|
||||
model.set_user_input(0, 2, 1, "25€".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "100.348€".to_string())
|
||||
.unwrap();
|
||||
model.set_user_input(0, 2, 1, "25€".to_string()).unwrap();
|
||||
|
||||
// negatives
|
||||
model.set_user_input(0, 1, 2, "-123.348€".to_string());
|
||||
model.set_user_input(0, 2, 2, "-42€".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 2, "-123.348€".to_string())
|
||||
.unwrap();
|
||||
model.set_user_input(0, 2, 2, "-42€".to_string()).unwrap();
|
||||
|
||||
// with a space
|
||||
model.set_user_input(0, 1, 3, "101.348 €".to_string());
|
||||
model.set_user_input(0, 2, 3, "26 €".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 3, "101.348 €".to_string())
|
||||
.unwrap();
|
||||
model.set_user_input(0, 2, 3, "26 €".to_string()).unwrap();
|
||||
|
||||
model.set_user_input(0, 1, 4, "-12.348 €".to_string());
|
||||
model.set_user_input(0, 2, 4, "-45 €".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 4, "-12.348 €".to_string())
|
||||
.unwrap();
|
||||
model.set_user_input(0, 2, 4, "-45 €".to_string()).unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -449,9 +503,15 @@ fn test_sum_function_eur() {
|
||||
model._set("A1", "€100");
|
||||
model._set("A2", "€300");
|
||||
|
||||
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
|
||||
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 2, "=SUM(A:A)".to_string())
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
@@ -463,7 +523,9 @@ fn test_sum_function_eur() {
|
||||
#[test]
|
||||
fn input_dates() {
|
||||
let mut model = new_empty_model();
|
||||
model.set_user_input(0, 1, 1, "3/4/2025".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "3/4/2025".to_string())
|
||||
.unwrap();
|
||||
|
||||
model.evaluate();
|
||||
|
||||
@@ -474,7 +536,9 @@ fn input_dates() {
|
||||
);
|
||||
|
||||
// further date assignments do not change the format
|
||||
model.set_user_input(0, 1, 1, "08-08-2028".to_string());
|
||||
model
|
||||
.set_user_input(0, 1, 1, "08-08-2028".to_string())
|
||||
.unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), "8/8/2028");
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ fn test_sheet_markup() {
|
||||
model._set("A4", "Total");
|
||||
model._set("B4", "=SUM(B2:B3)");
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
style.font.b = true;
|
||||
model.set_cell_style(0, 1, 1, &style).unwrap();
|
||||
model.set_cell_style(0, 1, 2, &style).unwrap();
|
||||
|
||||
@@ -138,14 +138,14 @@ fn test_insert_sheet() {
|
||||
|
||||
// Insert the sheet at the end and check the formula
|
||||
assert!(model.insert_sheet("Bacchus", 1, None).is_ok());
|
||||
model.set_user_input(1, 3, 1, "42".to_string());
|
||||
model.set_user_input(1, 3, 1, "42".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), "42");
|
||||
assert_eq!(model._get_text("A2"), "#REF!");
|
||||
|
||||
// Insert a sheet in between the other two
|
||||
assert!(model.insert_sheet("Dionysus", 1, None).is_ok());
|
||||
model.set_user_input(1, 3, 1, "111".to_string());
|
||||
model.set_user_input(1, 3, 1, "111".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), "42");
|
||||
assert_eq!(model._get_text("A2"), "111");
|
||||
@@ -176,7 +176,7 @@ fn test_rename_sheet() {
|
||||
let mut model = new_empty_model();
|
||||
model.new_sheet();
|
||||
model._set("A1", "=NewSheet!A3");
|
||||
model.set_user_input(1, 3, 1, "25".to_string());
|
||||
model.set_user_input(1, 3, 1, "25".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), "#REF!");
|
||||
assert!(model.rename_sheet("Sheet2", "NewSheet").is_ok());
|
||||
@@ -189,7 +189,7 @@ fn test_rename_sheet_by_index() {
|
||||
let mut model = new_empty_model();
|
||||
model.new_sheet();
|
||||
model._set("A1", "=NewSheet!A1");
|
||||
model.set_user_input(1, 1, 1, "25".to_string());
|
||||
model.set_user_input(1, 1, 1, "25".to_string()).unwrap();
|
||||
model.evaluate();
|
||||
assert_eq!(model._get_text("A1"), "#REF!");
|
||||
assert!(model.rename_sheet_by_index(1, "NewSheet").is_ok());
|
||||
|
||||
@@ -7,10 +7,10 @@ use crate::types::Style;
|
||||
fn test_model_set_cells_with_values_styles() {
|
||||
let mut model = new_empty_model();
|
||||
// Inputs
|
||||
model.set_user_input(0, 1, 1, "21".to_string()); // A1
|
||||
model.set_user_input(0, 2, 1, "42".to_string()); // A2
|
||||
model.set_user_input(0, 1, 1, "21".to_string()).unwrap(); // A1
|
||||
model.set_user_input(0, 2, 1, "42".to_string()).unwrap(); // A2
|
||||
|
||||
let style_base = model.get_style_for_cell(0, 1, 1);
|
||||
let style_base = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
let mut style = style_base.clone();
|
||||
style.font.b = true;
|
||||
style.num_fmt = "#,##0.00".to_string();
|
||||
@@ -19,7 +19,7 @@ fn test_model_set_cells_with_values_styles() {
|
||||
let mut style = style_base;
|
||||
style.num_fmt = "#,##0.00".to_string();
|
||||
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
|
||||
let style: Style = model.get_style_for_cell(0, 2, 1);
|
||||
let style: Style = model.get_style_for_cell(0, 2, 1).unwrap();
|
||||
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() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "42");
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
style.font.b = true;
|
||||
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
|
||||
let bold_style_index = model.get_cell_style_index(0, 1, 1);
|
||||
let bold_style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
||||
let e = model
|
||||
.workbook
|
||||
.styles
|
||||
.add_named_cell_style("bold", bold_style_index);
|
||||
assert!(e.is_ok());
|
||||
model._set("A2", "420");
|
||||
let a2_style_index = model.get_cell_style_index(0, 2, 1);
|
||||
let a2_style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
||||
assert!(a2_style_index != bold_style_index);
|
||||
let e = model.set_cell_style_by_name(0, 2, 1, "bold");
|
||||
assert!(e.is_ok());
|
||||
assert_eq!(model.get_cell_style_index(0, 2, 1), bold_style_index);
|
||||
assert_eq!(model.get_cell_style_index(0, 2, 1), Ok(bold_style_index));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -49,7 +49,7 @@ fn test_create_named_style() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "42");
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1);
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
|
||||
style.font.b = true;
|
||||
@@ -59,6 +59,48 @@ fn test_create_named_style() {
|
||||
let e = model.set_cell_style_by_name(0, 1, 1, "bold");
|
||||
assert!(e.is_ok());
|
||||
|
||||
let style = model.get_style_for_cell(0, 1, 1);
|
||||
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(style.font.b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_style_set_fns_in_merge_cell_context() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
// creating a merge cell of D1:F2
|
||||
model.merge_cells(0, "D1:F2").unwrap();
|
||||
model.set_user_input(0, 1, 4, "Hello".to_string()).unwrap();
|
||||
|
||||
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
||||
assert!(!style.font.b);
|
||||
style.font.b = true;
|
||||
|
||||
// Updating the mother cell of Merge cells and expecting the update to go through
|
||||
// This should make the text "Hello" in bold format
|
||||
assert_eq!(model.set_cell_style(0, 1, 4, &style), Ok(()));
|
||||
|
||||
// 1: testing with set_cell_style()
|
||||
let original_style: Style = model.get_style_for_cell(0, 1, 5).unwrap();
|
||||
assert_eq!(
|
||||
model
|
||||
.set_cell_style(0, 1, 5, &style),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_style_for_cell(0, 1, 5), Ok(original_style));
|
||||
|
||||
// 2: testing with set_cell_style_by_name
|
||||
let mut style = model.get_style_for_cell(0, 1, 4).unwrap();
|
||||
style.font.b = true;
|
||||
assert_eq!(
|
||||
model.workbook.styles.create_named_style("bold", &style),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let original_style: Style = model.get_style_for_cell(0, 1, 5).unwrap();
|
||||
assert_eq!(
|
||||
model
|
||||
.set_cell_style_by_name(0, 1, 5, "bold"),
|
||||
Err("Cell row : 1, col : 5 is part of merged cells block, so singular update to the cell is not possible".to_string())
|
||||
);
|
||||
assert_eq!(model.get_style_for_cell(0, 1, 5), Ok(original_style));
|
||||
}
|
||||
|
||||
@@ -100,7 +100,9 @@ fn test_worksheet_dimension_progressive() {
|
||||
}
|
||||
);
|
||||
|
||||
model.set_user_input(0, 30, 50, "Hello World".to_string());
|
||||
model
|
||||
.set_user_input(0, 30, 50, "Hello World".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().dimension(),
|
||||
WorksheetDimension {
|
||||
@@ -111,7 +113,9 @@ fn test_worksheet_dimension_progressive() {
|
||||
}
|
||||
);
|
||||
|
||||
model.set_user_input(0, 10, 15, "Hello World".to_string());
|
||||
model
|
||||
.set_user_input(0, 10, 15, "Hello World".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().dimension(),
|
||||
WorksheetDimension {
|
||||
@@ -122,7 +126,9 @@ fn test_worksheet_dimension_progressive() {
|
||||
}
|
||||
);
|
||||
|
||||
model.set_user_input(0, 5, 25, "Hello World".to_string());
|
||||
model
|
||||
.set_user_input(0, 5, 25, "Hello World".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().dimension(),
|
||||
WorksheetDimension {
|
||||
@@ -133,7 +139,9 @@ fn test_worksheet_dimension_progressive() {
|
||||
}
|
||||
);
|
||||
|
||||
model.set_user_input(0, 10, 250, "Hello World".to_string());
|
||||
model
|
||||
.set_user_input(0, 10, 250, "Hello World".to_string())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
model.workbook.worksheet(0).unwrap().dimension(),
|
||||
WorksheetDimension {
|
||||
@@ -162,12 +170,14 @@ fn test_worksheet_navigate_to_edge_in_direction() {
|
||||
for (row_index, row) in inline_spreadsheet.into_iter().enumerate() {
|
||||
for (column_index, value) in row.into_iter().enumerate() {
|
||||
if value != 0 {
|
||||
model.update_cell_with_number(
|
||||
0,
|
||||
(row_index as i32) + 1,
|
||||
(column_index as i32) + 1,
|
||||
value.into(),
|
||||
);
|
||||
model
|
||||
.update_cell_with_number(
|
||||
0,
|
||||
(row_index as i32) + 1,
|
||||
(column_index as i32) + 1,
|
||||
value.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,3 +283,87 @@ fn test_worksheet_navigate_to_edge_in_direction() {
|
||||
assert_eq!(navigate(8, 3, NavigationDirection::Up), (6, 3));
|
||||
assert_eq!(navigate(9, 3, NavigationDirection::Up), (6, 3));
|
||||
}
|
||||
|
||||
// Tests Merge cells related functions of worksheet
|
||||
|
||||
#[test]
|
||||
fn test_merge_cell_fns_worksheet() {
|
||||
let mut model = new_empty_model();
|
||||
|
||||
// Adding one Merge cell
|
||||
model.merge_cells(0, "D1:E3").unwrap();
|
||||
|
||||
// Lets check whether D1 (Mother Merge cell) is part of Merge block or not
|
||||
// It should not be considered as part of Merge cell
|
||||
assert!(!model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.is_part_of_merged_cells(1, 4)
|
||||
.unwrap(),);
|
||||
|
||||
// Lets give cell which is actually part of Merge block and expect true from fn
|
||||
assert!(model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.is_part_of_merged_cells(2, 4)
|
||||
.unwrap());
|
||||
|
||||
// Lets give cell which is not a part of Merge block and expect false from fn
|
||||
assert!(!model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.is_part_of_merged_cells(2, 6)
|
||||
.unwrap());
|
||||
|
||||
// Lets give an Invalid row
|
||||
assert_eq!(
|
||||
model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.is_part_of_merged_cells(0, 1),
|
||||
Err("Incorrect row or column".to_string())
|
||||
);
|
||||
|
||||
//Lets give Invalid column
|
||||
assert_eq!(
|
||||
model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.is_part_of_merged_cells(1, 0),
|
||||
Err("Incorrect row or column".to_string())
|
||||
);
|
||||
|
||||
// Verifying get fns of worksheet
|
||||
assert_eq!(
|
||||
model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.get_merged_cells_list()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
{
|
||||
let merge_cell_vec = model
|
||||
.workbook
|
||||
.worksheet_mut(0)
|
||||
.unwrap()
|
||||
.get_merged_cells_list_mut();
|
||||
merge_cell_vec.remove(0);
|
||||
|
||||
assert_eq!(
|
||||
model
|
||||
.workbook
|
||||
.worksheet(0)
|
||||
.unwrap()
|
||||
.get_merged_cells_list()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
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;
|
||||
mod util;
|
||||
|
||||
@@ -5,13 +5,13 @@ 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();
|
||||
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();
|
||||
model.new_sheet().unwrap();
|
||||
model.set_user_input(2, 1, 1, "=Sheet2!B1").unwrap();
|
||||
|
||||
model.undo().unwrap();
|
||||
@@ -59,7 +59,7 @@ fn set_sheet_color() {
|
||||
#[test]
|
||||
fn new_sheet_propagates() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.new_sheet();
|
||||
model.new_sheet().unwrap();
|
||||
|
||||
let send_queue = model.flush_send_queue();
|
||||
|
||||
@@ -72,7 +72,7 @@ fn new_sheet_propagates() {
|
||||
#[test]
|
||||
fn delete_sheet_propagates() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.new_sheet();
|
||||
model.new_sheet().unwrap();
|
||||
model.delete_sheet(0).unwrap();
|
||||
|
||||
let send_queue = model.flush_send_queue();
|
||||
@@ -82,3 +82,23 @@ fn delete_sheet_propagates() {
|
||||
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);
|
||||
}
|
||||
|
||||
404
base/src/test/user_model/test_autofill_columns.rs
Normal file
404
base/src/test/user_model/test_autofill_columns.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
#![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())
|
||||
);
|
||||
}
|
||||
399
base/src/test/user_model/test_autofill_rows.rs
Normal file
399
base/src/test/user_model/test_autofill_rows.rs
Normal file
@@ -0,0 +1,399 @@
|
||||
#![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())
|
||||
);
|
||||
}
|
||||
1034
base/src/test/user_model/test_border.rs
Normal file
1034
base/src/test/user_model/test_border.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{
|
||||
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT},
|
||||
test::util::new_empty_model,
|
||||
@@ -138,7 +140,7 @@ fn queue_undo_redo_multiple() {
|
||||
#[test]
|
||||
fn new_sheet() {
|
||||
let mut model1 = UserModel::from_model(new_empty_model());
|
||||
model1.new_sheet();
|
||||
model1.new_sheet().unwrap();
|
||||
model1.set_user_input(0, 1, 1, "42").unwrap();
|
||||
model1.set_user_input(1, 1, 1, "=Sheet1!A1*2").unwrap();
|
||||
|
||||
|
||||
@@ -129,3 +129,12 @@ fn delete_remove_cell() {
|
||||
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");
|
||||
}
|
||||
|
||||
42
base/src/test/user_model/test_grid_lines.rs
Normal file
42
base/src/test/user_model/test_grid_lines.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
#![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));
|
||||
}
|
||||
136
base/src/test/user_model/test_keyboard_navigation.rs
Normal file
136
base/src/test/user_model/test_keyboard_navigation.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
#![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);
|
||||
}
|
||||
33
base/src/test/user_model/test_on_area_selection.rs
Normal file
33
base/src/test/user_model/test_on_area_selection.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#![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);
|
||||
}
|
||||
151
base/src/test/user_model/test_on_expand_selected_range.rs
Normal file
151
base/src/test/user_model/test_on_expand_selected_range.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
#![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);
|
||||
}
|
||||
48
base/src/test/user_model/test_on_paste_styles.rs
Normal file
48
base/src/test/user_model/test_on_paste_styles.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
#![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);
|
||||
}
|
||||
}
|
||||
}
|
||||
187
base/src/test/user_model/test_paste_csv.rs
Normal file
187
base/src/test/user_model/test_paste_csv.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
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\t2\t3\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 csv_paste_formula() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
|
||||
let csv = "=YEAR(TODAY())";
|
||||
let area = Area {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
model.set_selected_cell(1, 1).unwrap();
|
||||
model.paste_csv_string(&area, csv).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_formatted_cell_value(0, 1, 1),
|
||||
Ok("2022".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\t \"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\t \"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\t127\n\"A season of faith\t \"\"perfection\"\"\"\t\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);
|
||||
}
|
||||
@@ -9,6 +9,13 @@ fn basic_rename() {
|
||||
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();
|
||||
|
||||
@@ -154,3 +154,21 @@ fn simple_delete_row_no_style() {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
expressions::types::Area,
|
||||
types::{Alignment, BorderItem, BorderStyle, HorizontalAlignment, VerticalAlignment},
|
||||
types::{Alignment, HorizontalAlignment, VerticalAlignment},
|
||||
UserModel,
|
||||
};
|
||||
|
||||
@@ -145,6 +145,7 @@ fn basic_fill() {
|
||||
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
|
||||
@@ -156,6 +157,7 @@ fn basic_fill() {
|
||||
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();
|
||||
|
||||
@@ -227,157 +229,6 @@ fn basic_format() {
|
||||
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();
|
||||
@@ -565,142 +416,6 @@ fn basic_wrap_text() {
|
||||
);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
209
base/src/test/user_model/test_view.rs
Normal file
209
base/src/test/user_model/test_view.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
#![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));
|
||||
}
|
||||
29
base/src/test/user_model/test_window_size.rs
Normal file
29
base/src/test/user_model/test_window_size.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#![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);
|
||||
}
|
||||
75
base/src/test/user_model/util.rs
Normal file
75
base/src/test/user_model/util.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{expressions::types::Area, types::Border, BorderArea, UserModel};
|
||||
|
||||
impl UserModel {
|
||||
pub fn _set_cell_border(&mut self, cell: &str, color: &str) {
|
||||
let cell_reference = self.model._parse_reference(cell);
|
||||
let column = cell_reference.column;
|
||||
let row = cell_reference.row;
|
||||
let border_area: BorderArea = serde_json::from_str(&format!(
|
||||
r##"{{
|
||||
"item": {{
|
||||
"style": "thin",
|
||||
"color": "{}"
|
||||
}},
|
||||
"type": "All"
|
||||
}}"##,
|
||||
color
|
||||
))
|
||||
.unwrap();
|
||||
let range = &Area {
|
||||
sheet: 0,
|
||||
row,
|
||||
column,
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
self.set_area_with_border(range, &border_area).unwrap();
|
||||
}
|
||||
|
||||
pub fn _set_area_border(&mut self, range: &str, color: &str, kind: &str) {
|
||||
let s: Vec<&str> = range.split(':').collect();
|
||||
let left = self.model._parse_reference(s[0]);
|
||||
let right = self.model._parse_reference(s[1]);
|
||||
let column = left.column;
|
||||
let row = left.row;
|
||||
let width = right.column - column + 1;
|
||||
let height = right.row - row + 1;
|
||||
let border_area: BorderArea = serde_json::from_str(&format!(
|
||||
r##"{{
|
||||
"item": {{
|
||||
"style": "thin",
|
||||
"color": "{}"
|
||||
}},
|
||||
"type": "{}"
|
||||
}}"##,
|
||||
color, kind
|
||||
))
|
||||
.unwrap();
|
||||
let range = &Area {
|
||||
sheet: 0,
|
||||
row,
|
||||
column,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
self.set_area_with_border(range, &border_area).unwrap();
|
||||
}
|
||||
|
||||
pub fn _get_cell_border(&self, cell: &str) -> Border {
|
||||
let cell_reference = self.model._parse_reference(cell);
|
||||
let column = cell_reference.column;
|
||||
let row = cell_reference.row;
|
||||
let style = self.get_cell_style(0, row, column).unwrap();
|
||||
style.border
|
||||
}
|
||||
|
||||
pub fn _get_cell_actual_border(&self, cell: &str) -> Border {
|
||||
let cell_reference = self.model._parse_reference(cell);
|
||||
let column = cell_reference.column;
|
||||
let row = cell_reference.row;
|
||||
let style = self.model.get_style_for_cell(0, row, column).unwrap();
|
||||
style.border
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ pub fn new_empty_model() -> Model {
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn _parse_reference(&self, cell: &str) -> CellReferenceIndex {
|
||||
pub fn _parse_reference(&self, cell: &str) -> CellReferenceIndex {
|
||||
if cell.contains('!') {
|
||||
self.parse_reference(cell).unwrap()
|
||||
} else {
|
||||
@@ -20,7 +20,8 @@ impl Model {
|
||||
let cell_reference = self._parse_reference(cell);
|
||||
let column = cell_reference.column;
|
||||
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 {
|
||||
self._get_formula_opt(cell).is_some()
|
||||
|
||||
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::expressions::token::Error;
|
||||
use crate::expressions::utils::number_to_column;
|
||||
|
||||
fn default_as_false() -> bool {
|
||||
false
|
||||
@@ -27,6 +28,18 @@ pub struct WorkbookSettings {
|
||||
pub tz: 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
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||
pub struct Workbook {
|
||||
@@ -38,6 +51,7 @@ pub struct Workbook {
|
||||
pub settings: WorkbookSettings,
|
||||
pub metadata: Metadata,
|
||||
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
|
||||
@@ -48,9 +62,6 @@ pub struct DefinedName {
|
||||
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:
|
||||
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
||||
/// hidden, veryHidden, visible
|
||||
@@ -71,12 +82,21 @@ 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 Selection {
|
||||
pub is_selected: bool,
|
||||
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
|
||||
@@ -91,11 +111,13 @@ pub struct Worksheet {
|
||||
pub sheet_id: u32,
|
||||
pub state: SheetState,
|
||||
pub color: Option<String>,
|
||||
pub merge_cells: Vec<String>,
|
||||
pub merged_cells_list: Vec<MergedCells>,
|
||||
pub comments: Vec<Comment>,
|
||||
pub frozen_rows: i32,
|
||||
pub frozen_columns: i32,
|
||||
pub selection: Selection,
|
||||
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
|
||||
@@ -330,6 +352,43 @@ pub enum FontScheme {
|
||||
None,
|
||||
}
|
||||
|
||||
// MergedCells type
|
||||
// There will be one MergedCells struct maintained for every Merged cells that we load
|
||||
// merge_cell_range : Its tuple having [row_start, column_start, row_end, column_end]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct MergedCells(pub i32, pub i32, pub i32, pub i32);
|
||||
|
||||
// implementing accessor function
|
||||
impl MergedCells {
|
||||
// Method which returns range_ref from the tuple
|
||||
// ex : (3,1,4,2) is interpreted as A3:B4
|
||||
pub fn get_merged_cells_str_ref(&self) -> Result<String, String> {
|
||||
let start_column = number_to_column(self.1).ok_or(format!(
|
||||
"Error while converting column start {} number to column string ref",
|
||||
self.1
|
||||
))?;
|
||||
let end_column = number_to_column(self.3).ok_or(format!(
|
||||
"Error while converting column end {} number to column string ref",
|
||||
self.3
|
||||
))?;
|
||||
return Ok(start_column
|
||||
+ &self.0.to_string()
|
||||
+ &":".to_string()
|
||||
+ &end_column
|
||||
+ &self.2.to_string());
|
||||
}
|
||||
|
||||
// Only Public function where Merge cell can be created
|
||||
pub fn new(merge_cell_parsed_range: (i32, i32, i32, i32)) -> Self {
|
||||
Self(
|
||||
merge_cell_parsed_range.0,
|
||||
merge_cell_parsed_range.1,
|
||||
merge_cell_parsed_range.2,
|
||||
merge_cell_parsed_range.3,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FontScheme {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
@@ -557,7 +616,7 @@ impl Default for CellStyles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum BorderStyle {
|
||||
Thin,
|
||||
|
||||
@@ -7,17 +7,20 @@ use crate::{
|
||||
|
||||
pub enum Units {
|
||||
Number {
|
||||
#[allow(dead_code)]
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
},
|
||||
Currency {
|
||||
#[allow(dead_code)]
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
currency: String,
|
||||
},
|
||||
Percentage {
|
||||
#[allow(dead_code)]
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
@@ -86,12 +89,15 @@ fn get_units_from_format_string(num_fmt: &str) -> Option<Units> {
|
||||
|
||||
impl Model {
|
||||
fn compute_cell_units(&self, cell_reference: &CellReferenceIndex) -> Option<Units> {
|
||||
let style = &self.get_style_for_cell(
|
||||
let cell_style_res = &self.get_style_for_cell(
|
||||
cell_reference.sheet,
|
||||
cell_reference.row,
|
||||
cell_reference.column,
|
||||
);
|
||||
get_units_from_format_string(&style.num_fmt)
|
||||
match cell_style_res {
|
||||
Ok(style) => get_units_from_format_string(&style.num_fmt),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compute_node_units(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
137
base/src/user_model/border_utils.rs
Normal file
137
base/src/user_model/border_utils.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use crate::types::BorderItem;
|
||||
|
||||
fn parse_color(s: &str) -> Option<(u8, u8, u8)> {
|
||||
let s = s.trim_start_matches('#');
|
||||
match s.len() {
|
||||
6 => {
|
||||
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
|
||||
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
|
||||
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
|
||||
Some((r, g, b))
|
||||
}
|
||||
3 => {
|
||||
let r = u8::from_str_radix(&s[0..1], 16).ok()?;
|
||||
let g = u8::from_str_radix(&s[1..2], 16).ok()?;
|
||||
let b = u8::from_str_radix(&s[2..3], 16).ok()?;
|
||||
// Expand single hex digits to full bytes
|
||||
Some((r * 17, g * 17, b * 17))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_luminance(r: u8, g: u8, b: u8) -> f64 {
|
||||
// Normalize RGB values to [0, 1]
|
||||
let r = r as f64 / 255.0;
|
||||
let g = g as f64 / 255.0;
|
||||
let b = b as f64 / 255.0;
|
||||
// Calculate luminance using the Rec. 601 formula
|
||||
0.299 * r + 0.587 * g + 0.114 * b
|
||||
}
|
||||
|
||||
fn is_max_color(a: &str, b: &str) -> bool {
|
||||
let (ar, ag, ab) = match parse_color(a) {
|
||||
Some(rgb) => rgb,
|
||||
None => return false, // Invalid color format for 'a'
|
||||
};
|
||||
|
||||
let (br, bg, bb) = match parse_color(b) {
|
||||
Some(rgb) => rgb,
|
||||
None => return false, // Invalid color format for 'b'
|
||||
};
|
||||
|
||||
let luminance_a = compute_luminance(ar, ag, ab);
|
||||
let luminance_b = compute_luminance(br, bg, bb);
|
||||
|
||||
// 'b' is heavier if its luminance is less than 'a's luminance
|
||||
luminance_b < luminance_a
|
||||
}
|
||||
|
||||
/// Is border b "heavier" than a?
|
||||
pub(crate) fn is_max_border(a: Option<&BorderItem>, b: Option<&BorderItem>) -> bool {
|
||||
match (a, b) {
|
||||
(_, None) => false,
|
||||
(None, Some(_)) => true,
|
||||
(Some(item_a), Some(item_b)) => {
|
||||
if item_a.style < item_b.style {
|
||||
return true;
|
||||
} else if item_a.style > item_b.style {
|
||||
return false;
|
||||
}
|
||||
match (&item_a.color, &item_b.color) {
|
||||
(_, None) => false,
|
||||
(None, Some(_)) => true,
|
||||
(Some(color_a), Some(color_b)) => is_max_color(color_a, color_b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::BorderStyle;
|
||||
|
||||
#[test]
|
||||
fn compare_borders() {
|
||||
let b = BorderItem {
|
||||
style: BorderStyle::Thin,
|
||||
color: Some("#FFF".to_string()),
|
||||
};
|
||||
// Some border *always* beats no border
|
||||
assert!(is_max_border(None, Some(&b)));
|
||||
|
||||
// No border is beaten by some border
|
||||
assert!(!is_max_border(Some(&b), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_colors() {
|
||||
// Black vs White
|
||||
assert!(is_max_color("#FFFFFF", "#000000"));
|
||||
assert!(!is_max_color("#000000", "#FFFFFF"));
|
||||
|
||||
// Red vs Dark Red
|
||||
assert!(is_max_color("#FF0000", "#800000"));
|
||||
assert!(!is_max_color("#800000", "#FF0000"));
|
||||
|
||||
// Green vs Dark Green
|
||||
assert!(is_max_color("#00FF00", "#008000"));
|
||||
assert!(!is_max_color("#008000", "#00FF00"));
|
||||
|
||||
// Blue vs Dark Blue
|
||||
assert!(is_max_color("#0000FF", "#000080"));
|
||||
assert!(!is_max_color("#000080", "#0000FF"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_color() {
|
||||
// Comparing the same color should return false
|
||||
assert!(!is_max_color("#123456", "#123456"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_cases() {
|
||||
// Colors with minimal luminance difference
|
||||
assert!(!is_max_color("#000000", "#010101"));
|
||||
assert!(!is_max_color("#FEFEFE", "#FFFFFF"));
|
||||
assert!(!is_max_color("#7F7F7F", "#808080"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn luminance_ordering() {
|
||||
// Colors with known luminance differences
|
||||
assert!(is_max_color("#CCCCCC", "#333333")); // Light gray vs Day
|
||||
assert!(is_max_color("#FFFF00", "#808000")); // Yellow ve
|
||||
assert!(is_max_color("#FF00FF", "#800080")); // Magenta vle
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borderline_cases() {
|
||||
// Testing colors with equal luminance
|
||||
assert!(!is_max_color("#777777", "#777777"));
|
||||
|
||||
// Testing black against near-black
|
||||
assert!(!is_max_color("#000000", "#010000"));
|
||||
}
|
||||
}
|
||||
2036
base/src/user_model/common.rs
Normal file
2036
base/src/user_model/common.rs
Normal file
File diff suppressed because it is too large
Load Diff
164
base/src/user_model/history.rs
Normal file
164
base/src/user_model/history.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
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,
|
||||
}
|
||||
14
base/src/user_model/mod.rs
Normal file
14
base/src/user_model/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod border_utils;
|
||||
mod common;
|
||||
mod history;
|
||||
mod ui;
|
||||
|
||||
pub use common::UserModel;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use ui::SelectedView;
|
||||
|
||||
pub use common::BorderArea;
|
||||
pub use common::ClipboardData;
|
||||
687
base/src/user_model/ui.rs
Normal file
687
base/src/user_model/ui.rs
Normal file
@@ -0,0 +1,687 @@
|
||||
#![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(())
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ impl ParsedReference {
|
||||
locale: &Locale,
|
||||
get_sheet_index_by_name: F,
|
||||
) -> Result<ParsedReference, String> {
|
||||
#[allow(clippy::expect_used)]
|
||||
let language = get_language("en").expect("");
|
||||
let mut lexer = Lexer::new(reference, LexerMode::A1, locale, language);
|
||||
|
||||
@@ -151,6 +152,8 @@ pub(crate) fn is_valid_hex_color(color: &str) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use super::*;
|
||||
use crate::language::get_language;
|
||||
use crate::locale::{get_locale, Locale};
|
||||
|
||||
@@ -42,7 +42,17 @@ impl Worksheet {
|
||||
self.sheet_data.get_mut(&row)?.get_mut(&column)
|
||||
}
|
||||
|
||||
pub(crate) fn update_cell(&mut self, row: i32, column: i32, new_cell: Cell) {
|
||||
pub(crate) fn update_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) {
|
||||
Some(column_data) => match column_data.get(&column) {
|
||||
Some(_cell) => {
|
||||
@@ -58,6 +68,7 @@ impl Worksheet {
|
||||
self.sheet_data.insert(row, column_data);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO [MVP]: Pass the cell style from the model
|
||||
@@ -128,53 +139,94 @@ impl Worksheet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_cell_style(&mut self, row: i32, column: i32, style_index: i32) {
|
||||
pub fn set_cell_style(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
style_index: i32,
|
||||
) -> Result<(), String> {
|
||||
match self.cell_mut(row, column) {
|
||||
Some(cell) => {
|
||||
cell.set_style(style_index);
|
||||
}
|
||||
None => {
|
||||
self.cell_clear_contents_with_style(row, column, style_index);
|
||||
self.cell_clear_contents_with_style(row, column, style_index)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
// TODO: cleanup check if the old cell style is still in use
|
||||
}
|
||||
|
||||
pub fn set_cell_with_formula(&mut self, row: i32, column: i32, index: i32, style: i32) {
|
||||
pub fn set_cell_with_formula(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
index: i32,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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(&mut self, row: i32, column: i32, value: f64, style: i32) {
|
||||
pub fn set_cell_with_number(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: f64,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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(&mut self, row: i32, column: i32, index: i32, style: i32) {
|
||||
pub fn set_cell_with_string(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
index: i32,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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(&mut self, row: i32, column: i32, value: bool, style: i32) {
|
||||
pub fn set_cell_with_boolean(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: bool,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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(&mut self, row: i32, column: i32, error: Error, style: i32) {
|
||||
pub fn set_cell_with_error(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
error: Error,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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) {
|
||||
pub fn cell_clear_contents(&mut self, row: i32, column: i32) -> Result<(), String> {
|
||||
let s = self.get_style(row, column);
|
||||
let cell = Cell::EmptyCell { s };
|
||||
self.update_cell(row, column, cell);
|
||||
self.update_cell(row, column, cell)
|
||||
}
|
||||
|
||||
pub fn cell_clear_contents_with_style(&mut self, row: i32, column: i32, style: i32) {
|
||||
pub fn cell_clear_contents_with_style(
|
||||
&mut self,
|
||||
row: i32,
|
||||
column: i32,
|
||||
style: i32,
|
||||
) -> Result<(), String> {
|
||||
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> {
|
||||
@@ -202,6 +254,7 @@ impl Worksheet {
|
||||
/// Changes the height of a row.
|
||||
/// * If the row does not a have a style we add it.
|
||||
/// * If it has we modify the height and make sure it is applied.
|
||||
///
|
||||
/// Fails if column index is outside allowed range.
|
||||
pub fn set_row_height(&mut self, row: i32, height: f64) -> Result<(), String> {
|
||||
if !is_valid_row(row) {
|
||||
@@ -230,6 +283,7 @@ impl Worksheet {
|
||||
/// Changes the width of a column.
|
||||
/// * If the column does not a have a width we simply add it
|
||||
/// * If it has, it might be part of a range and we ned to split the range.
|
||||
///
|
||||
/// Fails if column index is outside allowed range.
|
||||
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
|
||||
self.set_column_width_and_style(column, width, None)
|
||||
@@ -470,6 +524,23 @@ impl Worksheet {
|
||||
Ok(is_empty)
|
||||
}
|
||||
|
||||
/// Returns true if cell part of Merged Cells.
|
||||
/// First cell of Merged cells block is not considered as part of Merged cells
|
||||
/// Ex : if Merged cells were A1-C3, A1 is not considered as part of Merged cells block
|
||||
pub fn is_part_of_merged_cells(&self, row: i32, column: i32) -> Result<bool, String> {
|
||||
if !is_valid_column_number(column) || !is_valid_row(row) {
|
||||
return Err("Incorrect row or column".to_string());
|
||||
}
|
||||
|
||||
// traverse through Vector of Merged Cells and return (linear search)
|
||||
for merged_cells in &self.merged_cells_list {
|
||||
if merged_cells.is_cell_part_of_merged_cells(row, column) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// It provides convenient method for user navigation in the spreadsheet by jumping to edges.
|
||||
/// Spreadsheet engines usually allow this method of navigation by using CTRL+arrows.
|
||||
/// Behaviour summary:
|
||||
@@ -523,6 +594,16 @@ impl Worksheet {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns mutable reference to Vector of Merged cells list
|
||||
pub fn get_merged_cells_list_mut(&mut self) -> &mut Vec<MergedCells> {
|
||||
&mut self.merged_cells_list
|
||||
}
|
||||
|
||||
/// Returns reference to Vector of Merged cells list
|
||||
pub fn get_merged_cells_list(&self) -> &Vec<MergedCells> {
|
||||
&self.merged_cells_list
|
||||
}
|
||||
}
|
||||
|
||||
struct WalkFoundCells {
|
||||
|
||||
5
bindings/python/.gitignore
vendored
Normal file
5
bindings/python/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
target/*
|
||||
venv/*
|
||||
sphinx-venv/*
|
||||
html/*
|
||||
tests/__pycache*
|
||||
884
bindings/python/Cargo.lock
generated
Normal file
884
bindings/python/Cargo.lock
generated
Normal file
@@ -0,0 +1,884 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbc529705a6e0028189c83f0a5dd9fb214105116f7e3c0eeab7ff0369766b0d1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ironcalc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"ironcalc_base",
|
||||
"itertools",
|
||||
"roxmltree",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ironcalc_base"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"regex",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"parking_lot",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyroncalc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ironcalc",
|
||||
"pyo3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf7d7b1ea646d380d0e8153158063a6da7efe30ddbf3184042848e3f8a6f671"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
21
bindings/python/Cargo.toml
Normal file
21
bindings/python/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "pyroncalc"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[lib]
|
||||
name = "ironcalc"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
xlsx = { package= "ironcalc", path = "../../xlsx", version = "0.2.0" }
|
||||
pyo3 = { version = "0.22.3", features = ["extension-module"] }
|
||||
|
||||
|
||||
[features]
|
||||
extension-module = ["pyo3/extension-module"]
|
||||
default = ["extension-module"]
|
||||
191
bindings/python/LICENSE-Apache-2.0.md
Normal file
191
bindings/python/LICENSE-Apache-2.0.md
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2023 EqualTo GmbH, 2023 Nicolás Hatcher
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
9
bindings/python/LICENSE-MIT.md
Normal file
9
bindings/python/LICENSE-MIT.md
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 EqualTo GmbH, 2023 Nicolás Hatcher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
31
bindings/python/README.md
Normal file
31
bindings/python/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# IronCalc python bindings
|
||||
|
||||
With IronCalc you can create, read and manipulate xlsx files.
|
||||
You can manage sheets, add, remove, delete rename.
|
||||
You can add cell values, retrieve them and most importantly you can evaluate spreadsheets.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install ironcalc
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Compile and test
|
||||
|
||||
To compile this and test it:
|
||||
|
||||
```bash
|
||||
$ python3 -m venv venv
|
||||
$ source venv/bin/activate
|
||||
$ pip install maturin
|
||||
$ maturin develop
|
||||
$ cd examples
|
||||
examples $ python example.py
|
||||
```
|
||||
|
||||
From there if you use `python` you can `import ironcalc`. You can either create a new file, read it from a JSON string or import from Excel.
|
||||
|
||||
Hopefully the API is straightforward.
|
||||
14
bindings/python/docs/conf.py
Normal file
14
bindings/python/docs/conf.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
project = 'IronCalc'
|
||||
author = 'Nicolás Hatcher'
|
||||
|
||||
release = '0.1'
|
||||
version = '0.1.2'
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon']
|
||||
|
||||
templates_path = ['_templates']
|
||||
|
||||
exclude_patterns = []
|
||||
|
||||
html_theme = 'alabaster'
|
||||
8
bindings/python/docs/examples/simple.py
Normal file
8
bindings/python/docs/examples/simple.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import ironcalc as ic
|
||||
|
||||
model = ic.create("model", "en", "UTC")
|
||||
|
||||
model.set_user_input(0, 1, 1, "=21*2")
|
||||
model.evaluate()
|
||||
|
||||
assert model.get_formatted_cell_value(0, 1, 1), 42
|
||||
13
bindings/python/docs/index.rst
Normal file
13
bindings/python/docs/index.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
IronCalc: The democratization of spreadsheets
|
||||
=============================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
IronCalc is a spreadsheet engine that allows you to create, modify and safe spreadsheets.
|
||||
|
||||
A simple example that creates a model, sets a formula, evaluates it and gets the result back:
|
||||
|
||||
.. literalinclude:: examples/simple.py
|
||||
:language: python
|
||||
36
bindings/python/pyproject.toml
Normal file
36
bindings/python/pyproject.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[project]
|
||||
name = "ironcalc"
|
||||
version = "0.1.2"
|
||||
description = "Create, edit and evaluate Excel spreadsheets"
|
||||
requires-python = ">=3.10"
|
||||
keywords = [
|
||||
"xlsx",
|
||||
"spreadsheet",
|
||||
]
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Office/Business :: Financial :: Spreadsheet",
|
||||
]
|
||||
authors = [
|
||||
{ name = "Nicolás Hatcher", email = "nicolas@theuniverse.today" },
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.ironcalc.com/"
|
||||
Source = "https://github.com/ironcalc/ironcalc"
|
||||
Tracker = "https://github.com/ironcalc/ironcalc/issues"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module"]
|
||||
37
bindings/python/run_examples.sh
Executable file
37
bindings/python/run_examples.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Define the directory containing the Python files
|
||||
EXAMPLES_DIR="docs/examples"
|
||||
|
||||
# Check if the directory exists
|
||||
if [ ! -d "$EXAMPLES_DIR" ]; then
|
||||
echo "Directory $EXAMPLES_DIR does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
# not sure why this is needed
|
||||
pip install patchelf
|
||||
pip install maturin
|
||||
pip install pytest
|
||||
maturin develop
|
||||
|
||||
# Iterate over all Python files in the examples directory
|
||||
for file in "$EXAMPLES_DIR"/*.py; do
|
||||
# Check if there are any Python files
|
||||
if [ -f "$file" ]; then
|
||||
echo "Running $file..."
|
||||
python "$file"
|
||||
|
||||
# Check if the script ran successfully
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error running $file"
|
||||
else
|
||||
echo "$file ran successfully"
|
||||
fi
|
||||
else
|
||||
echo "No Python files found in $EXAMPLES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
9
bindings/python/run_tests.sh
Executable file
9
bindings/python/run_tests.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
# not sure why this is needed
|
||||
pip install patchelf
|
||||
pip install maturin
|
||||
pip install pytest
|
||||
maturin develop
|
||||
pytest tests/
|
||||
261
bindings/python/src/lib.rs
Normal file
261
bindings/python/src/lib.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use pyo3::exceptions::PyException;
|
||||
use pyo3::{create_exception, prelude::*, wrap_pyfunction};
|
||||
|
||||
use types::{PySheetProperty, PyStyle};
|
||||
use xlsx::base::types::Style;
|
||||
use xlsx::base::Model;
|
||||
|
||||
use xlsx::export::{save_to_icalc, save_to_xlsx};
|
||||
use xlsx::import;
|
||||
|
||||
mod types;
|
||||
|
||||
create_exception!(_ironcalc, WorkbookError, PyException);
|
||||
|
||||
/// This is a model implementing the 'raw' API
|
||||
#[pyclass]
|
||||
pub struct PyModel {
|
||||
model: Model,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyModel {
|
||||
/// Saves the model to an xlsx file
|
||||
pub fn save_to_xlsx(&self, file: &str) -> PyResult<()> {
|
||||
save_to_xlsx(&self.model, file).map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Saves the model to file in the internal binary ic format
|
||||
pub fn save_to_icalc(&self, file: &str) -> PyResult<()> {
|
||||
save_to_icalc(&self.model, file).map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Evaluates the workbook
|
||||
pub fn evaluate(&mut self) {
|
||||
self.model.evaluate()
|
||||
}
|
||||
|
||||
// Set values
|
||||
|
||||
/// Set an input
|
||||
pub fn set_user_input(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
value: &str,
|
||||
) -> PyResult<()> {
|
||||
self.model
|
||||
.set_user_input(sheet, row, column, value.to_string())
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn clear_cell_contents(&mut self, sheet: u32, row: i32, column: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.cell_clear_contents(sheet, row, column)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
// Get values
|
||||
|
||||
/// Get formatted value
|
||||
pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> PyResult<String> {
|
||||
self.model
|
||||
.get_formatted_cell_value(sheet, row, column)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
// Set styles
|
||||
pub fn set_cell_style(
|
||||
&mut self,
|
||||
sheet: u32,
|
||||
row: i32,
|
||||
column: i32,
|
||||
py_style: &PyStyle,
|
||||
) -> PyResult<()> {
|
||||
let style: Style = py_style.into();
|
||||
self.model
|
||||
.set_cell_style(sheet, row, column, &style)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
// Get styles
|
||||
pub fn get_cell_style(&self, sheet: u32, row: i32, column: i32) -> PyResult<PyStyle> {
|
||||
let style = self
|
||||
.model
|
||||
.get_style_for_cell(sheet, row, column)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(style.into())
|
||||
}
|
||||
|
||||
// column widths, row heights
|
||||
// insert/delete rows/columns
|
||||
|
||||
pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.insert_rows(sheet, row, row_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn insert_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.insert_columns(sheet, column, column_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.delete_rows(sheet, row, row_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn delete_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.delete_columns(sheet, column, column_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_column_width(&self, sheet: u32, column: i32) -> PyResult<f64> {
|
||||
self.model
|
||||
.get_column_width(sheet, column)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_row_height(&self, sheet: u32, row: i32) -> PyResult<f64> {
|
||||
self.model
|
||||
.get_row_height(sheet, row)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> PyResult<()> {
|
||||
self.model
|
||||
.set_column_width(sheet, column, width)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> PyResult<()> {
|
||||
self.model
|
||||
.set_row_height(sheet, row, height)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
// frozen rows/columns
|
||||
|
||||
pub fn get_frozen_columns_count(&self, sheet: u32) -> PyResult<i32> {
|
||||
self.model
|
||||
.get_frozen_columns_count(sheet)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_frozen_rows_count(&self, sheet: u32) -> PyResult<i32> {
|
||||
self.model
|
||||
.get_frozen_rows_count(sheet)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn set_frozen_columns_count(&mut self, sheet: u32, column_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.set_frozen_columns(sheet, column_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn set_frozen_rows_count(&mut self, sheet: u32, row_count: i32) -> PyResult<()> {
|
||||
self.model
|
||||
.set_frozen_rows(sheet, row_count)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
// Manipulate sheets (add/remove/rename/change color)
|
||||
pub fn get_worksheets_properties(&self) -> PyResult<Vec<PySheetProperty>> {
|
||||
Ok(self
|
||||
.model
|
||||
.get_worksheets_properties()
|
||||
.into_iter()
|
||||
.map(|s| PySheetProperty {
|
||||
name: s.name,
|
||||
state: s.state,
|
||||
sheet_id: s.sheet_id,
|
||||
color: s.color,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn set_sheet_color(&mut self, sheet: u32, color: &str) -> PyResult<()> {
|
||||
self.model
|
||||
.set_sheet_color(sheet, color)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn add_sheet(&mut self, sheet_name: &str) -> PyResult<()> {
|
||||
self.model
|
||||
.add_sheet(sheet_name)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn new_sheet(&mut self) {
|
||||
self.model.new_sheet();
|
||||
}
|
||||
|
||||
pub fn delete_sheet(&mut self, sheet: u32) -> PyResult<()> {
|
||||
self.model
|
||||
.delete_sheet(sheet)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn rename_sheet(&mut self, sheet: u32, new_name: &str) -> PyResult<()> {
|
||||
self.model
|
||||
.rename_sheet_by_index(sheet, new_name)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub fn test_panic(&self) -> PyResult<()> {
|
||||
panic!("This function panics for testing panic handling");
|
||||
}
|
||||
}
|
||||
|
||||
// Create methods
|
||||
|
||||
/// Loads a function from an xlsx file
|
||||
#[pyfunction]
|
||||
pub fn load_from_xlsx(file_path: &str, locale: &str, tz: &str) -> PyResult<PyModel> {
|
||||
let model = import::load_from_xlsx(file_path, locale, tz)
|
||||
.map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
/// Loads a function from icalc binary representation
|
||||
#[pyfunction]
|
||||
pub fn load_from_icalc(file_name: &str) -> PyResult<PyModel> {
|
||||
let model =
|
||||
import::load_from_icalc(file_name).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
/// Creates an empty model
|
||||
#[pyfunction]
|
||||
pub fn create(name: &str, locale: &str, tz: &str) -> PyResult<PyModel> {
|
||||
let model =
|
||||
Model::new_empty(name, locale, tz).map_err(|e| WorkbookError::new_err(e.to_string()))?;
|
||||
Ok(PyModel { model })
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(clippy::panic)]
|
||||
pub fn test_panic() {
|
||||
panic!("This function panics for testing panic handling");
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn ironcalc(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
// Add the package version to the module
|
||||
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
||||
|
||||
// Add the functions to the module using the `?` operator
|
||||
m.add_function(wrap_pyfunction!(create, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(load_from_xlsx, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(load_from_icalc, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(test_panic, m)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user