Compare commits
21 Commits
feature/ni
...
feature/ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd12881972 | ||
|
|
f752c90058 | ||
|
|
a78d5593f2 | ||
|
|
079208a1bd | ||
|
|
4721582dfe | ||
|
|
1746eec5da | ||
|
|
f9cf86a17c | ||
|
|
49ef846ebd | ||
|
|
b3b7dea930 | ||
|
|
196e074ef5 | ||
|
|
489027991c | ||
|
|
d445553d85 | ||
|
|
e9fc41541b | ||
|
|
c474bd2fc0 | ||
|
|
d6a1f9c28e | ||
|
|
5be13d1602 | ||
|
|
053217d3e4 | ||
|
|
493dc56892 | ||
|
|
a38ba93724 | ||
|
|
0029901ca3 | ||
|
|
1381533b9c |
18
.github/workflows/publish-wiki.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Publish wiki
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- wiki/**
|
||||||
|
- .github/workflows/publish-wiki.yml
|
||||||
|
concurrency:
|
||||||
|
group: publish-wiki
|
||||||
|
cancel-in-progress: true
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
jobs:
|
||||||
|
publish-wiki:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||||
3
.github/workflows/rust-build-test.yaml
vendored
@@ -16,6 +16,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install wasm-pack
|
||||||
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
target/*
|
target/*
|
||||||
|
.DS_Store
|
||||||
604
Cargo.lock
generated
@@ -9,10 +9,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aes"
|
||||||
version = "1.1.2"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -34,15 +45,57 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcode"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48bc1c27654127a24c476d40198746860ef56475f41a601bfa5c4d0f832968f0"
|
||||||
|
dependencies = [
|
||||||
|
"bitcode_derive",
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcode_derive"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2966755a19aad59ee2aae91e2d48842c667a99d818ec72168efdab07200701cc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.14.0"
|
version = "3.15.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -73,10 +126,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.83"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -88,9 +142,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.31"
|
version = "0.4.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
@@ -102,9 +156,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono-tz"
|
name = "chrono-tz"
|
||||||
version = "0.7.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbc529705a6e0028189c83f0a5dd9fb214105116f7e3c0eeab7ff0369766b0d1"
|
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz-build",
|
"chrono-tz-build",
|
||||||
@@ -113,9 +167,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono-tz-build"
|
name = "chrono-tz-build"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
|
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parse-zoneinfo",
|
"parse-zoneinfo",
|
||||||
"phf",
|
"phf",
|
||||||
@@ -123,25 +177,96 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "cipher"
|
||||||
version = "0.8.4"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.3.2"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "crossbeam-utils"
|
||||||
version = "1.9.0"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
@@ -154,21 +279,40 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "generic-array"
|
||||||
version = "0.2.11"
|
version = "0.14.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.58"
|
version = "0.1.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -187,10 +331,20 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironcalc"
|
name = "ironcalc"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitcode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"ironcalc_base",
|
"ironcalc_base",
|
||||||
"itertools",
|
"itertools",
|
||||||
@@ -204,8 +358,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironcalc_base"
|
name = "ironcalc_base"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitcode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -215,74 +370,88 @@ dependencies = [
|
|||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.65"
|
version = "0.3.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
|
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.150"
|
version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.20"
|
version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.6.4"
|
version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-conv"
|
||||||
version = "0.2.17"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parse-zoneinfo"
|
name = "parse-zoneinfo"
|
||||||
@@ -293,6 +462,29 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbkdf2"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
"password-hash",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@@ -333,9 +525,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.27"
|
version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
@@ -345,18 +543,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.69"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.33"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -393,9 +591,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.2"
|
version = "1.10.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -405,9 +603,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.3"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -416,39 +614,53 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.13.1"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbf7d7b1ea646d380d0e8153158063a6da7efe30ddbf3184042848e3f8a6f671"
|
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||||
dependencies = [
|
|
||||||
"xmlparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.15"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.192"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde-wasm-bindgen"
|
||||||
version = "1.0.192"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.197"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -457,9 +669,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.108"
|
version = "1.0.115"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -467,14 +679,25 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "sha1"
|
||||||
version = "0.1.17"
|
version = "0.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"cfg-if",
|
||||||
"quote",
|
"cpufeatures",
|
||||||
"syn",
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -484,10 +707,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "subtle"
|
||||||
version = "2.0.39"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -496,18 +725,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.50"
|
version = "1.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.50"
|
version = "1.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -516,15 +745,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.45"
|
version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"deranged",
|
||||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
"num-conv",
|
||||||
"winapi",
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@@ -533,19 +776,19 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.5.0"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "version_check"
|
||||||
version = "0.10.0+wasi-snapshot-preview1"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
@@ -553,11 +796,22 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm"
|
||||||
|
version = "0.1.3"
|
||||||
|
dependencies = [
|
||||||
|
"ironcalc_base",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-test",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.88"
|
version = "0.2.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
|
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
@@ -565,9 +819,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.88"
|
version = "0.2.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
|
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -579,10 +833,22 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.2.88"
|
version = "0.4.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
|
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -590,9 +856,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.88"
|
version = "0.2.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
|
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -603,46 +869,59 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.88"
|
version = "0.2.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
|
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "wasm-bindgen-test"
|
||||||
version = "0.3.9"
|
version = "0.3.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi-i686-pc-windows-gnu",
|
"console_error_panic_hook",
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
"js-sys",
|
||||||
|
"scoped-tls",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-bindgen-test-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
name = "wasm-bindgen-test-macro"
|
||||||
version = "0.4.0"
|
version = "0.3.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "web-sys"
|
||||||
version = "0.4.0"
|
version = "0.3.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.51.1"
|
version = "0.52.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
@@ -655,62 +934,91 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.52.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xmlparser"
|
|
||||||
version = "0.13.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "0.5.13"
|
version = "0.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
|
"constant_time_eq",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
"crossbeam-utils",
|
||||||
"flate2",
|
"flate2",
|
||||||
"thiserror",
|
"hmac",
|
||||||
|
"pbkdf2",
|
||||||
|
"sha1",
|
||||||
"time",
|
"time",
|
||||||
|
"zstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "5.0.2+zstd.1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.10+zstd.1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"base",
|
"base",
|
||||||
"xlsx",
|
"xlsx",
|
||||||
|
"bindings/wasm",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|||||||
14
Makefile
@@ -7,14 +7,18 @@ format:
|
|||||||
|
|
||||||
tests: lint
|
tests: lint
|
||||||
cargo test
|
cargo test
|
||||||
make remove-xlsx
|
./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
|
||||||
|
|
||||||
remove-xlsx:
|
remove-artifacts:
|
||||||
rm -f xlsx/hello-calc.xlsx
|
rm -f xlsx/hello-calc.xlsx
|
||||||
rm -f xlsx/hello-styles.xlsx
|
rm -f xlsx/hello-styles.xlsx
|
||||||
rm -f xlsx/widths-and-heights.xlsx
|
rm -f xlsx/widths-and-heights.xlsx
|
||||||
|
rm -f functions.md
|
||||||
|
|
||||||
clean: remove-xlsx
|
clean: remove-artifacts
|
||||||
cargo clean
|
cargo clean
|
||||||
rm -r -f base/target
|
rm -r -f base/target
|
||||||
rm -r -f xlsx/target
|
rm -r -f xlsx/target
|
||||||
@@ -27,6 +31,10 @@ coverage:
|
|||||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-C instrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
|
CARGO_INCREMENTAL=0 RUSTFLAGS='-C instrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
|
||||||
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
||||||
|
|
||||||
|
update-docs:
|
||||||
|
cargo build
|
||||||
|
./target/debug/documentation -o wiki/functions.md
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
cargo doc --no-deps
|
cargo doc --no-deps
|
||||||
|
|
||||||
|
|||||||
52
README.md
@@ -117,55 +117,7 @@ See more examples in the `examples` folder of the xlsx crate.
|
|||||||
|
|
||||||
# ROADMAP
|
# ROADMAP
|
||||||
|
|
||||||
> [!WARNING]
|
See https://github.com/ironcalc
|
||||||
> This is work-in-progress. IronCalc in developed in the open. Expect things to be broken and change quickly until version 0.5
|
|
||||||
|
|
||||||
Major milestones:
|
|
||||||
|
|
||||||
* MVP, version 0.5.0: We intend to have a working version by mid March 2024 (version 0.5, MVP)
|
|
||||||
* Stable, version 1.0.0 will come later in December 2024
|
|
||||||
|
|
||||||
MVP stands for _Minimum Viable Product_
|
|
||||||
|
|
||||||
## Version 0.5 or MVP (early 2024)
|
|
||||||
|
|
||||||
Version 0.5 includes the engine, javascript and nodejs bindings and a web application
|
|
||||||
|
|
||||||
Features of the engine include:
|
|
||||||
|
|
||||||
* Read and write xlsx files
|
|
||||||
* API to set and read values from cells
|
|
||||||
* Implemented 192 Excel functions
|
|
||||||
* Time functions with timezones
|
|
||||||
* Prepared for i18n but will only support English
|
|
||||||
* Wide test coverage
|
|
||||||
|
|
||||||
UI features of the web application (backed by the engine):
|
|
||||||
|
|
||||||
* Enter values and formulas. Browse mode
|
|
||||||
* Italics, bold, underline, horizontal alignment
|
|
||||||
* Number formatting
|
|
||||||
* Add/remove/rename sheets
|
|
||||||
* Copy/Paste extend values
|
|
||||||
* Keyboard navigation
|
|
||||||
* Delete/Add rows and columns
|
|
||||||
* Resize rows and columns
|
|
||||||
* Correct scrolling and navigation
|
|
||||||
|
|
||||||
## Version 1.0 or Stable (December 2024)
|
|
||||||
|
|
||||||
Minor milestones in the ROADMAD for version 1.0.0 (engine and UI):
|
|
||||||
|
|
||||||
* Implementation of arrays and array formulas
|
|
||||||
* Formula documentation and context help
|
|
||||||
* Merge cells
|
|
||||||
* Pivot tables
|
|
||||||
* Define name manager (mostly UI)
|
|
||||||
* Update main evaluation algorithm with a support graph
|
|
||||||
* Dynamic arrays (SORT, UNIQUE, ..)
|
|
||||||
* Full i18n support with different locales and languages
|
|
||||||
* Python bindings
|
|
||||||
* Full test coverage
|
|
||||||
|
|
||||||
# Early testing
|
# Early testing
|
||||||
|
|
||||||
@@ -192,4 +144,4 @@ Licensed under either of
|
|||||||
* [MIT license](LICENSE-MIT)
|
* [MIT license](LICENSE-MIT)
|
||||||
* [Apache license, version 2.0](LICENSE-Apache-2.0)
|
* [Apache license, version 2.0](LICENSE-Apache-2.0)
|
||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|||||||
BIN
assets/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
assets/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 15 KiB |
BIN
assets/icon/ironcalc_icon.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
8
assets/icon/ironcalc_icon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="600" height="600" rx="20" fill="#F2994A"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 100C348.98 166.034 322.748 229.362 276.055 276.055C268.163 283.947 259.796 291.255 251.021 297.95L251.021 500L348.98 500H251.021C251.021 433.966 277.252 370.637 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05L348.98 100Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M251.021 100.068C251.003 140.096 235.094 178.481 206.788 206.787C178.466 235.109 140.053 251.02 100 251.02V348.979C154.873 348.979 207.877 330.866 251.021 297.95V100.068Z" fill="white"/>
|
||||||
|
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M348.98 499.882C349.011 459.872 364.918 421.507 393.213 393.213C421.534 364.891 459.947 348.98 500 348.98V251.02C445.128 251.02 392.123 269.134 348.98 302.05V499.882Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.055 276.055C322.748 229.362 348.98 166.034 348.98 100H251.021V297.95C259.796 291.255 268.163 283.947 276.055 276.055Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M348.98 302.05V499.895C348.98 499.93 348.98 499.965 348.98 500L251.021 500C251.021 499.946 251.02 499.891 251.021 499.837C251.064 433.862 277.291 370.599 323.945 323.945C331.837 316.053 340.204 308.745 348.98 302.05Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/logo.png
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 33 KiB |
BIN
assets/logo/png/black.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/logo/png/orange+black.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/logo/png/orange+white.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/logo/png/white.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
8
assets/logo/svg/black.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/orange+black.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/orange+white.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
8
assets/logo/svg/white.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironcalc_base"
|
name = "ironcalc_base"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://www.ironcalc.com"
|
homepage = "https://www.ironcalc.com"
|
||||||
@@ -12,18 +12,20 @@ readme = "README.md"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
|
||||||
serde_repr = "0.1"
|
|
||||||
ryu = "1.0"
|
ryu = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
chrono-tz = "0.7.0"
|
chrono-tz = "0.9"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
|
bitcode = "0.6.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = { version = "0.3.60" }
|
js-sys = { version = "0.3.69" }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
rand = "0.8.4"
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use ironcalc_base::{model::Model, types::CellType};
|
use ironcalc_base::{types::CellType, Model};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use ironcalc_base::{cell::CellValue, model::Model};
|
use ironcalc_base::{cell::CellValue, Model};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
||||||
|
|||||||
@@ -69,21 +69,26 @@ impl Model {
|
|||||||
target_row: i32,
|
target_row: i32,
|
||||||
target_column: i32,
|
target_column: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let source_cell = self
|
if let Some(source_cell) = self
|
||||||
.workbook
|
.workbook
|
||||||
.worksheet(sheet)?
|
.worksheet(sheet)?
|
||||||
.cell(source_row, source_column)
|
.cell(source_row, source_column)
|
||||||
.ok_or("Expected Cell to exist")?;
|
{
|
||||||
let style = source_cell.get_style();
|
let style = source_cell.get_style();
|
||||||
// FIXME: we need some user_input getter instead of get_text
|
// FIXME: we need some user_input getter instead of get_text
|
||||||
let formula_or_value = self
|
let formula_or_value = self
|
||||||
.cell_formula(sheet, source_row, source_column)?
|
.get_cell_formula(sheet, source_row, source_column)?
|
||||||
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
|
.unwrap_or_else(|| {
|
||||||
self.set_user_input(sheet, target_row, target_column, formula_or_value);
|
source_cell.get_text(&self.workbook.shared_strings, &self.language)
|
||||||
self.workbook
|
});
|
||||||
.worksheet_mut(sheet)?
|
self.set_user_input(sheet, target_row, target_column, formula_or_value);
|
||||||
.set_cell_style(target_row, target_column, style);
|
self.workbook
|
||||||
self.delete_cell(sheet, source_row, source_column)?;
|
.worksheet_mut(sheet)?
|
||||||
|
.set_cell_style(target_row, target_column, style);
|
||||||
|
self.cell_clear_all(sheet, source_row, source_column)?;
|
||||||
|
} else {
|
||||||
|
self.cell_clear_all(sheet, target_row, target_column)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +111,7 @@ impl Model {
|
|||||||
return Err("Cannot add a negative number of cells :)".to_string());
|
return Err("Cannot add a negative number of cells :)".to_string());
|
||||||
}
|
}
|
||||||
// check if it is possible:
|
// check if it is possible:
|
||||||
let dimensions = self.workbook.worksheet(sheet)?.dimension();
|
let dimensions = self.workbook.worksheet(sheet)?.get_dimension();
|
||||||
let last_column = dimensions.max_column + column_count;
|
let last_column = dimensions.max_column + column_count;
|
||||||
if last_column > LAST_COLUMN {
|
if last_column > LAST_COLUMN {
|
||||||
return Err(
|
return Err(
|
||||||
@@ -157,6 +162,11 @@ impl Model {
|
|||||||
return Err("Please use insert columns instead".to_string());
|
return Err("Please use insert columns instead".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// first column being deleted
|
||||||
|
let column_start = column;
|
||||||
|
// last column being deleted
|
||||||
|
let column_end = column + column_count - 1;
|
||||||
|
|
||||||
// Move cells
|
// Move cells
|
||||||
let worksheet = &self.workbook.worksheet(sheet)?;
|
let worksheet = &self.workbook.worksheet(sheet)?;
|
||||||
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
|
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
|
||||||
@@ -166,11 +176,11 @@ impl Model {
|
|||||||
for r in all_rows {
|
for r in all_rows {
|
||||||
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
|
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
|
||||||
for col in columns {
|
for col in columns {
|
||||||
if col >= column {
|
if col >= column_start {
|
||||||
if col >= column + column_count {
|
if col > column_end {
|
||||||
self.move_cell(sheet, r, col, r, col - column_count)?;
|
self.move_cell(sheet, r, col, r, col - column_count)?;
|
||||||
} else {
|
} else {
|
||||||
self.delete_cell(sheet, r, col)?;
|
self.cell_clear_all(sheet, r, col)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +194,64 @@ impl Model {
|
|||||||
delta: -column_count,
|
delta: -column_count,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||||
|
|
||||||
|
// deletes all the column styles
|
||||||
|
let mut new_columns = Vec::new();
|
||||||
|
for col in worksheet.cols.iter_mut() {
|
||||||
|
// range under study
|
||||||
|
let min = col.min;
|
||||||
|
let max = col.max;
|
||||||
|
// In the diagram:
|
||||||
|
// |xxxxx| range we are studying [min, max]
|
||||||
|
// |*****| range we are deleting [column_start, column_end]
|
||||||
|
// we are going to split it in three big cases:
|
||||||
|
// ----------------|xxxxxxxx|-----------------
|
||||||
|
// -----|*****|------------------------------- Case A
|
||||||
|
// -------|**********|------------------------ Case B
|
||||||
|
// -------------|**************|-------------- Case C
|
||||||
|
// ------------------|****|------------------- Case D
|
||||||
|
// ---------------------|**********|---------- Case E
|
||||||
|
// -----------------------------|*****|------- Case F
|
||||||
|
if column_start < min {
|
||||||
|
if column_end < min {
|
||||||
|
// Case A
|
||||||
|
// We displace all columns
|
||||||
|
let mut new_column = col.clone();
|
||||||
|
new_column.min = min - column_count;
|
||||||
|
new_column.max = max - column_count;
|
||||||
|
new_columns.push(new_column);
|
||||||
|
} else if column_end < max {
|
||||||
|
// Case B
|
||||||
|
// We displace the end
|
||||||
|
let mut new_column = col.clone();
|
||||||
|
new_column.min = column_start;
|
||||||
|
new_column.max = max - column_count;
|
||||||
|
new_columns.push(new_column);
|
||||||
|
} else {
|
||||||
|
// Case C
|
||||||
|
// skip this, we are deleting the whole range
|
||||||
|
}
|
||||||
|
} else if column_start <= max {
|
||||||
|
if column_end <= max {
|
||||||
|
// Case D
|
||||||
|
// We displace the end
|
||||||
|
let mut new_column = col.clone();
|
||||||
|
new_column.max = max - column_count;
|
||||||
|
new_columns.push(new_column);
|
||||||
|
} else {
|
||||||
|
// Case E
|
||||||
|
let mut new_column = col.clone();
|
||||||
|
new_column.max = column_start - 1;
|
||||||
|
new_columns.push(new_column);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Case F
|
||||||
|
// No action required
|
||||||
|
new_columns.push(col.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
worksheet.cols = new_columns;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -200,7 +268,7 @@ impl Model {
|
|||||||
return Err("Cannot add a negative number of cells :)".to_string());
|
return Err("Cannot add a negative number of cells :)".to_string());
|
||||||
}
|
}
|
||||||
// Check if it is possible:
|
// Check if it is possible:
|
||||||
let dimensions = self.workbook.worksheet(sheet)?.dimension();
|
let dimensions = self.workbook.worksheet(sheet)?.get_dimension();
|
||||||
let last_row = dimensions.max_row + row_count;
|
let last_row = dimensions.max_row + row_count;
|
||||||
if last_row > LAST_ROW {
|
if last_row > LAST_ROW {
|
||||||
return Err(
|
return Err(
|
||||||
@@ -283,7 +351,7 @@ impl Model {
|
|||||||
// remove all cells in row
|
// remove all cells in row
|
||||||
// FIXME: We could just remove the entire row in one go
|
// FIXME: We could just remove the entire row in one go
|
||||||
for column in columns {
|
for column in columns {
|
||||||
self.delete_cell(sheet, r, column)?;
|
self.cell_clear_all(sheet, r, column)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,13 +372,162 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.workbook.worksheets[sheet as usize].rows = new_rows;
|
self.workbook.worksheets[sheet as usize].rows = new_rows;
|
||||||
self.displace_cells(
|
self.displace_cells(&DisplaceData::Row {
|
||||||
&(DisplaceData::Row {
|
sheet,
|
||||||
sheet,
|
row,
|
||||||
row,
|
delta: -row_count,
|
||||||
delta: -row_count,
|
});
|
||||||
}),
|
Ok(())
|
||||||
);
|
}
|
||||||
|
|
||||||
|
pub fn delete_cells_and_shift_left(
|
||||||
|
&mut self,
|
||||||
|
sheet: u32,
|
||||||
|
row: i32,
|
||||||
|
column: i32,
|
||||||
|
row_delta: i32,
|
||||||
|
column_delta: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||||
|
let max_column = worksheet.get_dimension().max_column;
|
||||||
|
|
||||||
|
// Delete all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in column..column + column_delta {
|
||||||
|
self.cell_clear_all(sheet, r, c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in column + 1..max_column + 1 {
|
||||||
|
println!("{r}-{c}");
|
||||||
|
self.move_cell(sheet, r, c, r, c - column_delta)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all formulas in the workbook
|
||||||
|
self.displace_cells(&DisplaceData::ShiftCellsRight {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
column_delta: -column_delta,
|
||||||
|
row_delta,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert cells and shift right
|
||||||
|
pub fn insert_cells_and_shift_right(
|
||||||
|
&mut self,
|
||||||
|
sheet: u32,
|
||||||
|
row: i32,
|
||||||
|
column: i32,
|
||||||
|
row_delta: i32,
|
||||||
|
column_delta: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||||
|
let max_column = worksheet.get_dimension().max_column;
|
||||||
|
|
||||||
|
// Move all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in (column..max_column + 1).rev() {
|
||||||
|
self.move_cell(sheet, r, c, r, c + column_delta)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in column..column + column_delta {
|
||||||
|
self.cell_clear_all(sheet, r, c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all formulas in the workbook
|
||||||
|
self.displace_cells(&DisplaceData::ShiftCellsRight {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
column_delta,
|
||||||
|
row_delta,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert cells and shift down
|
||||||
|
pub fn insert_cells_and_shift_down(
|
||||||
|
&mut self,
|
||||||
|
sheet: u32,
|
||||||
|
row: i32,
|
||||||
|
column: i32,
|
||||||
|
row_delta: i32,
|
||||||
|
column_delta: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||||
|
let max_row = worksheet.get_dimension().max_row;
|
||||||
|
|
||||||
|
// Move all cells in the range
|
||||||
|
for r in (row..row + max_row + 1).rev() {
|
||||||
|
for c in column..column + column_delta {
|
||||||
|
self.move_cell(sheet, r, c, r, c + column_delta)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in column..column + column_delta {
|
||||||
|
self.cell_clear_all(sheet, r, c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all formulas in the workbook
|
||||||
|
self.displace_cells(&DisplaceData::ShiftCellsDown {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
column_delta,
|
||||||
|
row_delta,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_cells_and_shift_up(
|
||||||
|
&mut self,
|
||||||
|
sheet: u32,
|
||||||
|
row: i32,
|
||||||
|
column: i32,
|
||||||
|
row_delta: i32,
|
||||||
|
column_delta: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
|
||||||
|
let max_row = worksheet.get_dimension().max_row;
|
||||||
|
|
||||||
|
// Delete all cells in the range
|
||||||
|
for r in row..row + row_delta {
|
||||||
|
for c in column..column + column_delta {
|
||||||
|
self.cell_clear_all(sheet, r, c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all cells in the range
|
||||||
|
for r in row..max_row + 1 {
|
||||||
|
for c in column + 1..column + column_delta {
|
||||||
|
self.move_cell(sheet, r, c, r - row_delta, c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all formulas in the workbook
|
||||||
|
self.displace_cells(&DisplaceData::ShiftCellsDown {
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
column_delta,
|
||||||
|
row_delta: -row_delta,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
/// A CellValue is the representation of the cell content.
|
/// A CellValue is the representation of the cell content.
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum CellValue {
|
pub enum CellValue {
|
||||||
None,
|
None,
|
||||||
String(String),
|
String(String),
|
||||||
@@ -14,17 +11,6 @@ pub enum CellValue {
|
|||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellValue {
|
|
||||||
pub fn to_json_str(&self) -> String {
|
|
||||||
match &self {
|
|
||||||
CellValue::None => "null".to_string(),
|
|
||||||
CellValue::String(s) => json!(s).to_string(),
|
|
||||||
CellValue::Number(f) => json!(f).to_string(),
|
|
||||||
CellValue::Boolean(b) => json!(b).to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for CellValue {
|
impl From<f64> for CellValue {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
Self::Number(value)
|
Self::Number(value)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::expressions::token;
|
use crate::expressions::token;
|
||||||
@@ -9,8 +11,11 @@ use super::{Lexer, LexerMode};
|
|||||||
/// A MarkedToken is a token together with its position on a formula
|
/// A MarkedToken is a token together with its position on a formula
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MarkedToken {
|
pub struct MarkedToken {
|
||||||
|
/// Token type (see [token::TokenType])
|
||||||
pub token: token::TokenType,
|
pub token: token::TokenType,
|
||||||
|
/// Position of the start of the token (in bytes)
|
||||||
pub start: i32,
|
pub start: i32,
|
||||||
|
/// Position of the end of the token (in bytes)
|
||||||
pub end: i32,
|
pub end: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +24,7 @@ pub struct MarkedToken {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use ironcalc_base::expressions::{
|
/// use ironcalc_base::expressions::{
|
||||||
/// lexer::util::{get_tokens, MarkedToken},
|
/// lexer::marked_token::{get_tokens, MarkedToken},
|
||||||
/// token::{OpSum, TokenType},
|
/// token::{OpSum, TokenType},
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
//! A tokenizer for spreadsheet formulas.
|
//! A tokenizer for spreadsheet formulas.
|
||||||
//!
|
//!
|
||||||
//! This is meant to feed a formula parser.
|
//! This is meant to feed a formula parser.
|
||||||
@@ -7,8 +9,10 @@
|
|||||||
//! It supports two working modes:
|
//! It supports two working modes:
|
||||||
//!
|
//!
|
||||||
//! 1. A1 or display mode
|
//! 1. A1 or display mode
|
||||||
|
//!
|
||||||
//! This is for user formulas. References are like `D4`, `D$4` or `F5:T10`
|
//! This is for user formulas. References are like `D4`, `D$4` or `F5:T10`
|
||||||
//! 2. R1C1, internal or runtime mode
|
//! 2. R1C1, internal or runtime mode
|
||||||
|
//!
|
||||||
//! A reference like R1C1 refers to $A$1 and R3C4 to $D$4
|
//! A reference like R1C1 refers to $A$1 and R3C4 to $D$4
|
||||||
//! R[2]C[5] refers to a cell two rows below and five columns to the right
|
//! R[2]C[5] refers to a cell two rows below and five columns to the right
|
||||||
//! It uses the 'en' locale and language.
|
//! It uses the 'en' locale and language.
|
||||||
@@ -55,7 +59,8 @@ use super::token::{Error, TokenType};
|
|||||||
use super::types::*;
|
use super::types::*;
|
||||||
use super::utils;
|
use super::utils;
|
||||||
|
|
||||||
pub mod util;
|
/// Returns an iterator over tokens together with their position in the byte string.
|
||||||
|
pub mod marked_token;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
@@ -63,17 +68,28 @@ mod test;
|
|||||||
mod ranges;
|
mod ranges;
|
||||||
mod structured_references;
|
mod structured_references;
|
||||||
|
|
||||||
|
/// This is the TokenType we return if we cannot recognize a token
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct LexerError {
|
pub struct LexerError {
|
||||||
|
/// Position of the beginning of the token in the byte string.
|
||||||
pub position: usize,
|
pub position: usize,
|
||||||
|
/// Message describing what we think the error is.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) type Result<T> = std::result::Result<T, LexerError>;
|
pub(super) type Result<T> = std::result::Result<T, LexerError>;
|
||||||
|
|
||||||
|
/// Whether we try to parse formulas in A1 mode or in the internal R1C1 mode
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub enum LexerMode {
|
pub enum LexerMode {
|
||||||
|
/// Cell references are written `=S34`. This is the display mode
|
||||||
A1,
|
A1,
|
||||||
|
/// R1C1, internal or runtime mode
|
||||||
|
///
|
||||||
|
/// A reference like R1C1 refers to $A$1 and R3C4 to $D$4
|
||||||
|
/// R[2]C[5] refers to a cell two rows below and five columns to the right
|
||||||
|
/// It uses the 'en' locale and language.
|
||||||
|
/// This is used internally at runtime.
|
||||||
R1C1,
|
R1C1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,9 +324,9 @@ impl Lexer {
|
|||||||
return self.consume_range(None);
|
return self.consume_range(None);
|
||||||
}
|
}
|
||||||
let name_upper = name.to_ascii_uppercase();
|
let name_upper = name.to_ascii_uppercase();
|
||||||
if name_upper == self.language.booleans.true_value {
|
if name_upper == self.language.booleans.r#true {
|
||||||
return TokenType::Boolean(true);
|
return TokenType::Boolean(true);
|
||||||
} else if name_upper == self.language.booleans.false_value {
|
} else if name_upper == self.language.booleans.r#false {
|
||||||
return TokenType::Boolean(false);
|
return TokenType::Boolean(false);
|
||||||
}
|
}
|
||||||
if self.mode == LexerMode::A1 {
|
if self.mode == LexerMode::A1 {
|
||||||
@@ -660,8 +676,8 @@ impl Lexer {
|
|||||||
fn consume_error(&mut self) -> TokenType {
|
fn consume_error(&mut self) -> TokenType {
|
||||||
let errors = &self.language.errors;
|
let errors = &self.language.errors;
|
||||||
let rest_of_formula: String = self.chars[self.position - 1..self.len].iter().collect();
|
let rest_of_formula: String = self.chars[self.position - 1..self.len].iter().collect();
|
||||||
if rest_of_formula.starts_with(&errors.ref_value) {
|
if rest_of_formula.starts_with(&errors.r#ref) {
|
||||||
self.position += errors.ref_value.chars().count() - 1;
|
self.position += errors.r#ref.chars().count() - 1;
|
||||||
return TokenType::Error(Error::REF);
|
return TokenType::Error(Error::REF);
|
||||||
} else if rest_of_formula.starts_with(&errors.name) {
|
} else if rest_of_formula.starts_with(&errors.name) {
|
||||||
self.position += errors.name.chars().count() - 1;
|
self.position += errors.name.chars().count() - 1;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mod test_common;
|
mod test_common;
|
||||||
mod test_language;
|
mod test_language;
|
||||||
mod test_locale;
|
mod test_locale;
|
||||||
|
mod test_marked_token;
|
||||||
mod test_ranges;
|
mod test_ranges;
|
||||||
mod test_tables;
|
mod test_tables;
|
||||||
mod test_util;
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use crate::{
|
|||||||
token::TokenType,
|
token::TokenType,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale_fix,
|
locale::get_locale,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn new_language_lexer(formula: &str, locale: &str, language: &str) -> Lexer {
|
fn new_language_lexer(formula: &str, locale: &str, language: &str) -> Lexer {
|
||||||
let locale = get_locale_fix(locale).unwrap();
|
let locale = get_locale(locale).unwrap();
|
||||||
let language = get_language(language).unwrap();
|
let language = get_language(language).unwrap();
|
||||||
Lexer::new(formula, LexerMode::A1, locale, language)
|
Lexer::new(formula, LexerMode::A1, locale, language)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::expressions::{
|
use crate::expressions::{
|
||||||
lexer::util::get_tokens,
|
lexer::marked_token::{get_tokens, MarkedToken},
|
||||||
token::{OpCompare, OpSum, TokenType},
|
token::{OpCompare, OpSum, TokenType},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,6 +22,29 @@ fn test_get_tokens() {
|
|||||||
assert_eq!(l.end, 10);
|
assert_eq!(l.end, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chinese_characters() {
|
||||||
|
let formula = "\"你好\" & \"世界!\"";
|
||||||
|
let marked_tokens = get_tokens(formula);
|
||||||
|
assert_eq!(marked_tokens.len(), 3);
|
||||||
|
let first_t = MarkedToken {
|
||||||
|
token: TokenType::String("你好".to_string()),
|
||||||
|
start: 0,
|
||||||
|
end: 4,
|
||||||
|
};
|
||||||
|
let second_t = MarkedToken {
|
||||||
|
token: TokenType::And,
|
||||||
|
start: 4,
|
||||||
|
end: 6,
|
||||||
|
};
|
||||||
|
let third_t = MarkedToken {
|
||||||
|
token: TokenType::String("世界!".to_string()),
|
||||||
|
start: 6,
|
||||||
|
end: 12,
|
||||||
|
};
|
||||||
|
assert_eq!(marked_tokens, vec![first_t, second_t, third_t]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_tokens() {
|
fn test_simple_tokens() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// public modules
|
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
/*!
|
//! # GRAMMAR
|
||||||
# GRAMAR
|
//!
|
||||||
|
//! <pre class="rust">
|
||||||
<pre class="rust">
|
//! opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
|
||||||
opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
|
//! opFactor => '*' | '/'
|
||||||
opFactor => '*' | '/'
|
//! unaryOp => '-' | '+'
|
||||||
unaryOp => '-' | '+'
|
//!
|
||||||
|
//! expr => concat (opComp concat)*
|
||||||
expr => concat (opComp concat)*
|
//! concat => term ('&' term)*
|
||||||
concat => term ('&' term)*
|
//! term => factor (opFactor factor)*
|
||||||
term => factor (opFactor factor)*
|
//! factor => prod (opProd prod)*
|
||||||
factor => prod (opProd prod)*
|
//! prod => power ('^' power)*
|
||||||
prod => power ('^' power)*
|
//! power => (unaryOp)* range '%'*
|
||||||
power => (unaryOp)* range '%'*
|
//! range => primary (':' primary)?
|
||||||
range => primary (':' primary)?
|
//! primary => '(' expr ')'
|
||||||
primary => '(' expr ')'
|
//! => number
|
||||||
=> number
|
//! => function '(' f_args ')'
|
||||||
=> function '(' f_args ')'
|
//! => name
|
||||||
=> name
|
//! => string
|
||||||
=> string
|
//! => '{' a_args '}'
|
||||||
=> '{' a_args '}'
|
//! => bool
|
||||||
=> bool
|
//! => bool()
|
||||||
=> bool()
|
//! => error
|
||||||
=> error
|
//!
|
||||||
|
//! f_args => e (',' e)*
|
||||||
f_args => e (',' e)*
|
//! </pre>
|
||||||
</pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -44,21 +42,15 @@ use super::utils::number_to_column;
|
|||||||
|
|
||||||
use token::OpCompare;
|
use token::OpCompare;
|
||||||
|
|
||||||
pub mod move_formula;
|
pub(crate) mod move_formula;
|
||||||
|
pub(crate) mod walk;
|
||||||
|
|
||||||
|
/// Produces a string representation of a formula from the AST.
|
||||||
pub mod stringify;
|
pub mod stringify;
|
||||||
pub mod walk;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_ranges;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_move_formula;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_tables;
|
|
||||||
|
|
||||||
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
|
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
|
||||||
let mut lexer = lexer::Lexer::new(
|
let mut lexer = lexer::Lexer::new(
|
||||||
formula,
|
formula,
|
||||||
@@ -222,7 +214,7 @@ impl Parser {
|
|||||||
|
|
||||||
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
||||||
self.lexer.set_formula(formula);
|
self.lexer.set_formula(formula);
|
||||||
self.context = context.clone();
|
self.context.clone_from(context);
|
||||||
self.parse_expr()
|
self.parse_expr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,75 @@
|
|||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use super::{super::utils::quote_name, Node, Reference};
|
use super::{super::utils::quote_name, Node, Reference};
|
||||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||||
use crate::expressions::token::OpUnary;
|
use crate::expressions::token::OpUnary;
|
||||||
use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
|
use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
|
||||||
|
|
||||||
|
/// Displaced data
|
||||||
pub enum DisplaceData {
|
pub enum DisplaceData {
|
||||||
|
/// Displaces columns (inserting or deleting columns)
|
||||||
Column {
|
Column {
|
||||||
|
/// Sheet in which the displace data applies
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
|
/// Column from which the data is displaced
|
||||||
column: i32,
|
column: i32,
|
||||||
|
/// Number of columns displaced (might be negative, e.g. when deleting columns)
|
||||||
delta: i32,
|
delta: i32,
|
||||||
},
|
},
|
||||||
|
/// Displaces rows (Inserting or deleting rows)
|
||||||
Row {
|
Row {
|
||||||
|
/// Sheet in which the displace data applies
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
|
/// Row from which the data is displaced
|
||||||
row: i32,
|
row: i32,
|
||||||
|
/// Number of rows displaced (might be negative, e.g. when deleting rows)
|
||||||
delta: i32,
|
delta: i32,
|
||||||
},
|
},
|
||||||
CellHorizontal {
|
/// Displaces cells horizontally
|
||||||
|
ShiftCellsRight {
|
||||||
|
/// Sheet in which the displace data applies
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
|
/// Row of the to left corner
|
||||||
row: i32,
|
row: i32,
|
||||||
|
/// Column of the top left corner
|
||||||
column: i32,
|
column: i32,
|
||||||
delta: i32,
|
/// Number of rows to be displaced
|
||||||
|
row_delta: i32,
|
||||||
|
/// Number of columns to be displaced (might be negative)
|
||||||
|
column_delta: i32,
|
||||||
},
|
},
|
||||||
CellVertical {
|
/// Displaces cells vertically
|
||||||
|
ShiftCellsDown {
|
||||||
|
/// Sheet in which the displace data applies
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
|
/// Row of the to left corner
|
||||||
row: i32,
|
row: i32,
|
||||||
|
/// Column of the top left corner
|
||||||
column: i32,
|
column: i32,
|
||||||
delta: i32,
|
/// Number of rows displaced (might be negative)
|
||||||
|
row_delta: i32,
|
||||||
|
/// Number of columns to be displaced
|
||||||
|
column_delta: i32,
|
||||||
},
|
},
|
||||||
|
/// Displaces data due to a column move from column to column + delta
|
||||||
ColumnMove {
|
ColumnMove {
|
||||||
|
/// Sheet in which the displace data applies
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
|
/// Column that is moved
|
||||||
column: i32,
|
column: i32,
|
||||||
|
/// The position of the new column is column + delta (might be negative)
|
||||||
delta: i32,
|
delta: i32,
|
||||||
},
|
},
|
||||||
|
/// Doesn't do any cell displacement
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringifies the AST formula in its internal R1C1 format
|
||||||
pub fn to_rc_format(node: &Node) -> String {
|
pub fn to_rc_format(node: &Node) -> String {
|
||||||
stringify(node, None, &DisplaceData::None, false)
|
stringify(node, None, &DisplaceData::None, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringifies the formula applying the _displace_data_.
|
||||||
pub fn to_string_displaced(
|
pub fn to_string_displaced(
|
||||||
node: &Node,
|
node: &Node,
|
||||||
context: &CellReferenceRC,
|
context: &CellReferenceRC,
|
||||||
@@ -46,10 +78,12 @@ pub fn to_string_displaced(
|
|||||||
stringify(node, Some(context), displace_data, false)
|
stringify(node, Some(context), displace_data, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringifies a formula from the AST
|
||||||
pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
|
pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
|
||||||
stringify(node, Some(context), &DisplaceData::None, false)
|
stringify(node, Some(context), &DisplaceData::None, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stringifies the formula for Excel compatibility
|
||||||
pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
|
pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
|
||||||
stringify(node, Some(context), &DisplaceData::None, true)
|
stringify(node, Some(context), &DisplaceData::None, true)
|
||||||
}
|
}
|
||||||
@@ -116,41 +150,49 @@ pub(crate) fn stringify_reference(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisplaceData::CellHorizontal {
|
DisplaceData::ShiftCellsRight {
|
||||||
sheet,
|
sheet,
|
||||||
row: displace_row,
|
row: displace_row,
|
||||||
column: displace_column,
|
column: displace_column,
|
||||||
delta,
|
column_delta,
|
||||||
|
row_delta,
|
||||||
} => {
|
} => {
|
||||||
if sheet_index == *sheet && displace_row == &row {
|
if sheet_index == *sheet
|
||||||
if *delta < 0 {
|
&& displace_row >= &row
|
||||||
|
&& *displace_row < row + *row_delta
|
||||||
|
{
|
||||||
|
if *column_delta < 0 {
|
||||||
if &column >= displace_column {
|
if &column >= displace_column {
|
||||||
if column < displace_column - *delta {
|
if column < displace_column - *column_delta {
|
||||||
return "#REF!".to_string();
|
return "#REF!".to_string();
|
||||||
}
|
}
|
||||||
column += *delta;
|
column += *column_delta;
|
||||||
}
|
}
|
||||||
} else if &column >= displace_column {
|
} else if &column >= displace_column {
|
||||||
column += *delta;
|
column += *column_delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisplaceData::CellVertical {
|
DisplaceData::ShiftCellsDown {
|
||||||
sheet,
|
sheet,
|
||||||
row: displace_row,
|
row: displace_row,
|
||||||
column: displace_column,
|
column: displace_column,
|
||||||
delta,
|
row_delta,
|
||||||
|
column_delta,
|
||||||
} => {
|
} => {
|
||||||
if sheet_index == *sheet && displace_column == &column {
|
if sheet_index == *sheet
|
||||||
if *delta < 0 {
|
&& displace_column >= &column
|
||||||
|
&& *displace_column < column + *column_delta
|
||||||
|
{
|
||||||
|
if *row_delta < 0 {
|
||||||
if &row >= displace_row {
|
if &row >= displace_row {
|
||||||
if row < displace_row - *delta {
|
if row < displace_row - *row_delta {
|
||||||
return "#REF!".to_string();
|
return "#REF!".to_string();
|
||||||
}
|
}
|
||||||
row += *delta;
|
row += *row_delta;
|
||||||
}
|
}
|
||||||
} else if &row >= displace_row {
|
} else if &row >= displace_row {
|
||||||
row += *delta;
|
row += *row_delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
base/src/expressions/parser/test/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mod test_genertal;
|
||||||
|
mod test_move_formula;
|
||||||
|
mod test_ranges;
|
||||||
|
mod test_tables;
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::expressions::lexer::LexerMode;
|
use crate::expressions::lexer::LexerMode;
|
||||||
use crate::expressions::parser::stringify::DisplaceData;
|
use crate::expressions::parser::stringify::{to_string_displaced, DisplaceData};
|
||||||
|
use crate::expressions::parser::{
|
||||||
use super::super::types::CellReferenceRC;
|
stringify::{to_rc_format, to_string},
|
||||||
use super::Parser;
|
Node, Parser,
|
||||||
use super::{
|
|
||||||
super::parser::{
|
|
||||||
stringify::{to_rc_format, to_string},
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
stringify::to_string_displaced,
|
|
||||||
};
|
};
|
||||||
|
use crate::expressions::types::CellReferenceRC;
|
||||||
|
|
||||||
struct Formula<'a> {
|
struct Formula<'a> {
|
||||||
initial: &'a str,
|
initial: &'a str,
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::expressions::parser::move_formula::{move_formula, MoveContext};
|
use crate::expressions::parser::move_formula::{move_formula, MoveContext};
|
||||||
use crate::expressions::types::Area;
|
use crate::expressions::parser::Parser;
|
||||||
|
use crate::expressions::types::{Area, CellReferenceRC};
|
||||||
use super::super::types::CellReferenceRC;
|
|
||||||
use super::Parser;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_move_formula() {
|
fn test_move_formula() {
|
||||||
@@ -2,9 +2,9 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::expressions::lexer::LexerMode;
|
use crate::expressions::lexer::LexerMode;
|
||||||
|
|
||||||
use super::super::parser::stringify::{to_rc_format, to_string};
|
use crate::expressions::parser::stringify::{to_rc_format, to_string};
|
||||||
use super::super::types::CellReferenceRC;
|
use crate::expressions::parser::Parser;
|
||||||
use super::Parser;
|
use crate::expressions::types::CellReferenceRC;
|
||||||
|
|
||||||
struct Formula<'a> {
|
struct Formula<'a> {
|
||||||
formula_a1: &'a str,
|
formula_a1: &'a str,
|
||||||
@@ -6,8 +6,8 @@ use crate::expressions::parser::stringify::to_string;
|
|||||||
use crate::expressions::utils::{number_to_column, parse_reference_a1};
|
use crate::expressions::utils::{number_to_column, parse_reference_a1};
|
||||||
use crate::types::{Table, TableColumn, TableStyleInfo};
|
use crate::types::{Table, TableColumn, TableStyleInfo};
|
||||||
|
|
||||||
use super::super::types::CellReferenceRC;
|
use crate::expressions::parser::Parser;
|
||||||
use super::Parser;
|
use crate::expressions::types::CellReferenceRC;
|
||||||
|
|
||||||
fn create_test_table(
|
fn create_test_table(
|
||||||
table_name: &str,
|
table_name: &str,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use bitcode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
|
||||||
|
|
||||||
use crate::language::Language;
|
use crate::language::Language;
|
||||||
|
|
||||||
@@ -80,8 +80,7 @@ impl fmt::Display for OpProduct {
|
|||||||
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
|
||||||
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
|
||||||
/// Note that they are serialized/deserialized by index
|
/// Note that they are serialized/deserialized by index
|
||||||
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
REF,
|
REF,
|
||||||
NAME,
|
NAME,
|
||||||
@@ -119,7 +118,7 @@ impl Error {
|
|||||||
pub fn to_localized_error_string(&self, language: &Language) -> String {
|
pub fn to_localized_error_string(&self, language: &Language) -> String {
|
||||||
match self {
|
match self {
|
||||||
Error::NULL => language.errors.null.to_string(),
|
Error::NULL => language.errors.null.to_string(),
|
||||||
Error::REF => language.errors.ref_value.to_string(),
|
Error::REF => language.errors.r#ref.to_string(),
|
||||||
Error::NAME => language.errors.name.to_string(),
|
Error::NAME => language.errors.name.to_string(),
|
||||||
Error::VALUE => language.errors.value.to_string(),
|
Error::VALUE => language.errors.value.to_string(),
|
||||||
Error::DIV => language.errors.div.to_string(),
|
Error::DIV => language.errors.div.to_string(),
|
||||||
@@ -136,7 +135,7 @@ impl Error {
|
|||||||
|
|
||||||
pub fn get_error_by_name(name: &str, language: &Language) -> Option<Error> {
|
pub fn get_error_by_name(name: &str, language: &Language) -> Option<Error> {
|
||||||
let errors = &language.errors;
|
let errors = &language.errors;
|
||||||
if name == errors.ref_value {
|
if name == errors.r#ref {
|
||||||
return Some(Error::REF);
|
return Some(Error::REF);
|
||||||
} else if name == errors.name {
|
} else if name == errors.name {
|
||||||
return Some(Error::NAME);
|
return Some(Error::NAME);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use chrono::Months;
|
use chrono::Months;
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
use chrono::TimeZone;
|
|
||||||
use chrono::Timelike;
|
use chrono::Timelike;
|
||||||
|
|
||||||
use crate::expressions::types::CellReferenceIndex;
|
use crate::expressions::types::CellReferenceIndex;
|
||||||
@@ -258,8 +257,8 @@ impl Model {
|
|||||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
||||||
Some(dt) => dt,
|
Some(dt) => dt.with_timezone(&self.tz),
|
||||||
None => {
|
None => {
|
||||||
return CalcResult::Error {
|
return CalcResult::Error {
|
||||||
error: Error::ERROR,
|
error: Error::ERROR,
|
||||||
@@ -268,7 +267,6 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let local_time = self.tz.from_utc_datetime(&dt);
|
|
||||||
// 693_594 is computed as:
|
// 693_594 is computed as:
|
||||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||||
// The 2 days offset is because of Excel 1900 bug
|
// The 2 days offset is because of Excel 1900 bug
|
||||||
@@ -289,8 +287,8 @@ impl Model {
|
|||||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
||||||
Some(dt) => dt,
|
Some(dt) => dt.with_timezone(&self.tz),
|
||||||
None => {
|
None => {
|
||||||
return CalcResult::Error {
|
return CalcResult::Error {
|
||||||
error: Error::ERROR,
|
error: Error::ERROR,
|
||||||
@@ -299,7 +297,6 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let local_time = self.tz.from_utc_datetime(&dt);
|
|
||||||
// 693_594 is computed as:
|
// 693_594 is computed as:
|
||||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||||
// The 2 days offset is because of Excel 1900 bug
|
// The 2 days offset is because of Excel 1900 bug
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt;
|
|||||||
use crate::{
|
use crate::{
|
||||||
calc_result::CalcResult,
|
calc_result::CalcResult,
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::util::get_tokens,
|
lexer::marked_token::get_tokens,
|
||||||
parser::Node,
|
parser::Node,
|
||||||
token::{Error, OpSum, TokenType},
|
token::{Error, OpSum, TokenType},
|
||||||
types::CellReferenceIndex,
|
types::CellReferenceIndex,
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -229,7 +229,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
@@ -284,7 +284,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -292,7 +292,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
@@ -360,7 +360,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -368,7 +368,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
@@ -866,7 +866,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -874,7 +874,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ impl Model {
|
|||||||
message: "argument must be a reference to a single cell".to_string(),
|
message: "argument must be a reference to a single cell".to_string(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let is_formula = if let Ok(f) = self.cell_formula(left.sheet, left.row, left.column) {
|
let is_formula = if let Ok(f) = self.get_cell_formula(left.sheet, left.row, left.column)
|
||||||
|
{
|
||||||
f.is_some()
|
f.is_some()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -140,7 +140,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
@@ -199,7 +199,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -207,7 +207,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use std::array::IntoIter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_result::CalcResult,
|
calc_result::CalcResult,
|
||||||
@@ -244,6 +245,206 @@ pub enum Function {
|
|||||||
Subtotal,
|
Subtotal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn into_iter() -> IntoIter<Function, 192> {
|
||||||
|
[
|
||||||
|
Function::And,
|
||||||
|
Function::False,
|
||||||
|
Function::If,
|
||||||
|
Function::Iferror,
|
||||||
|
Function::Ifna,
|
||||||
|
Function::Ifs,
|
||||||
|
Function::Not,
|
||||||
|
Function::Or,
|
||||||
|
Function::Switch,
|
||||||
|
Function::True,
|
||||||
|
Function::Xor,
|
||||||
|
Function::Sin,
|
||||||
|
Function::Cos,
|
||||||
|
Function::Tan,
|
||||||
|
Function::Asin,
|
||||||
|
Function::Acos,
|
||||||
|
Function::Atan,
|
||||||
|
Function::Sinh,
|
||||||
|
Function::Cosh,
|
||||||
|
Function::Tanh,
|
||||||
|
Function::Asinh,
|
||||||
|
Function::Acosh,
|
||||||
|
Function::Atanh,
|
||||||
|
Function::Abs,
|
||||||
|
Function::Pi,
|
||||||
|
Function::Sqrt,
|
||||||
|
Function::Sqrtpi,
|
||||||
|
Function::Atan2,
|
||||||
|
Function::Power,
|
||||||
|
Function::Max,
|
||||||
|
Function::Min,
|
||||||
|
Function::Product,
|
||||||
|
Function::Rand,
|
||||||
|
Function::Randbetween,
|
||||||
|
Function::Round,
|
||||||
|
Function::Rounddown,
|
||||||
|
Function::Roundup,
|
||||||
|
Function::Sum,
|
||||||
|
Function::Sumif,
|
||||||
|
Function::Sumifs,
|
||||||
|
Function::Choose,
|
||||||
|
Function::Column,
|
||||||
|
Function::Columns,
|
||||||
|
Function::Index,
|
||||||
|
Function::Indirect,
|
||||||
|
Function::Hlookup,
|
||||||
|
Function::Lookup,
|
||||||
|
Function::Match,
|
||||||
|
Function::Offset,
|
||||||
|
Function::Row,
|
||||||
|
Function::Rows,
|
||||||
|
Function::Vlookup,
|
||||||
|
Function::Xlookup,
|
||||||
|
Function::Concatenate,
|
||||||
|
Function::Exact,
|
||||||
|
Function::Value,
|
||||||
|
Function::T,
|
||||||
|
Function::Valuetotext,
|
||||||
|
Function::Concat,
|
||||||
|
Function::Find,
|
||||||
|
Function::Left,
|
||||||
|
Function::Len,
|
||||||
|
Function::Lower,
|
||||||
|
Function::Mid,
|
||||||
|
Function::Right,
|
||||||
|
Function::Search,
|
||||||
|
Function::Text,
|
||||||
|
Function::Trim,
|
||||||
|
Function::Upper,
|
||||||
|
Function::Isnumber,
|
||||||
|
Function::Isnontext,
|
||||||
|
Function::Istext,
|
||||||
|
Function::Islogical,
|
||||||
|
Function::Isblank,
|
||||||
|
Function::Iserr,
|
||||||
|
Function::Iserror,
|
||||||
|
Function::Isna,
|
||||||
|
Function::Na,
|
||||||
|
Function::Isref,
|
||||||
|
Function::Isodd,
|
||||||
|
Function::Iseven,
|
||||||
|
Function::ErrorType,
|
||||||
|
Function::Isformula,
|
||||||
|
Function::Type,
|
||||||
|
Function::Sheet,
|
||||||
|
Function::Average,
|
||||||
|
Function::Averagea,
|
||||||
|
Function::Averageif,
|
||||||
|
Function::Averageifs,
|
||||||
|
Function::Count,
|
||||||
|
Function::Counta,
|
||||||
|
Function::Countblank,
|
||||||
|
Function::Countif,
|
||||||
|
Function::Countifs,
|
||||||
|
Function::Maxifs,
|
||||||
|
Function::Minifs,
|
||||||
|
Function::Year,
|
||||||
|
Function::Day,
|
||||||
|
Function::Month,
|
||||||
|
Function::Eomonth,
|
||||||
|
Function::Date,
|
||||||
|
Function::Edate,
|
||||||
|
Function::Today,
|
||||||
|
Function::Now,
|
||||||
|
Function::Pmt,
|
||||||
|
Function::Pv,
|
||||||
|
Function::Rate,
|
||||||
|
Function::Nper,
|
||||||
|
Function::Fv,
|
||||||
|
Function::Ppmt,
|
||||||
|
Function::Ipmt,
|
||||||
|
Function::Npv,
|
||||||
|
Function::Mirr,
|
||||||
|
Function::Irr,
|
||||||
|
Function::Xirr,
|
||||||
|
Function::Xnpv,
|
||||||
|
Function::Rept,
|
||||||
|
Function::Textafter,
|
||||||
|
Function::Textbefore,
|
||||||
|
Function::Textjoin,
|
||||||
|
Function::Substitute,
|
||||||
|
Function::Ispmt,
|
||||||
|
Function::Rri,
|
||||||
|
Function::Sln,
|
||||||
|
Function::Syd,
|
||||||
|
Function::Nominal,
|
||||||
|
Function::Effect,
|
||||||
|
Function::Pduration,
|
||||||
|
Function::Tbillyield,
|
||||||
|
Function::Tbillprice,
|
||||||
|
Function::Tbilleq,
|
||||||
|
Function::Dollarde,
|
||||||
|
Function::Dollarfr,
|
||||||
|
Function::Ddb,
|
||||||
|
Function::Db,
|
||||||
|
Function::Cumprinc,
|
||||||
|
Function::Cumipmt,
|
||||||
|
Function::Besseli,
|
||||||
|
Function::Besselj,
|
||||||
|
Function::Besselk,
|
||||||
|
Function::Bessely,
|
||||||
|
Function::Erf,
|
||||||
|
Function::ErfPrecise,
|
||||||
|
Function::Erfc,
|
||||||
|
Function::ErfcPrecise,
|
||||||
|
Function::Bin2dec,
|
||||||
|
Function::Bin2hex,
|
||||||
|
Function::Bin2oct,
|
||||||
|
Function::Dec2Bin,
|
||||||
|
Function::Dec2hex,
|
||||||
|
Function::Dec2oct,
|
||||||
|
Function::Hex2bin,
|
||||||
|
Function::Hex2dec,
|
||||||
|
Function::Hex2oct,
|
||||||
|
Function::Oct2bin,
|
||||||
|
Function::Oct2dec,
|
||||||
|
Function::Oct2hex,
|
||||||
|
Function::Bitand,
|
||||||
|
Function::Bitlshift,
|
||||||
|
Function::Bitor,
|
||||||
|
Function::Bitrshift,
|
||||||
|
Function::Bitxor,
|
||||||
|
Function::Complex,
|
||||||
|
Function::Imabs,
|
||||||
|
Function::Imaginary,
|
||||||
|
Function::Imargument,
|
||||||
|
Function::Imconjugate,
|
||||||
|
Function::Imcos,
|
||||||
|
Function::Imcosh,
|
||||||
|
Function::Imcot,
|
||||||
|
Function::Imcsc,
|
||||||
|
Function::Imcsch,
|
||||||
|
Function::Imdiv,
|
||||||
|
Function::Imexp,
|
||||||
|
Function::Imln,
|
||||||
|
Function::Imlog10,
|
||||||
|
Function::Imlog2,
|
||||||
|
Function::Impower,
|
||||||
|
Function::Improduct,
|
||||||
|
Function::Imreal,
|
||||||
|
Function::Imsec,
|
||||||
|
Function::Imsech,
|
||||||
|
Function::Imsin,
|
||||||
|
Function::Imsinh,
|
||||||
|
Function::Imsqrt,
|
||||||
|
Function::Imsub,
|
||||||
|
Function::Imsum,
|
||||||
|
Function::Imtan,
|
||||||
|
Function::Convert,
|
||||||
|
Function::Delta,
|
||||||
|
Function::Gestep,
|
||||||
|
Function::Subtotal,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
|
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
|
||||||
pub fn to_xlsx_string(&self) -> String {
|
pub fn to_xlsx_string(&self) -> String {
|
||||||
@@ -708,7 +909,23 @@ impl fmt::Display for Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Documentation for one function
|
||||||
|
pub struct Documentation {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
|
/// Produces documentation for all implemented functions
|
||||||
|
pub fn documentation() -> Vec<Documentation> {
|
||||||
|
let mut doc = Vec::new();
|
||||||
|
for function in Function::into_iter() {
|
||||||
|
doc.push(Documentation {
|
||||||
|
name: function.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
doc
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn evaluate_function(
|
pub(crate) fn evaluate_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
kind: &Function,
|
kind: &Function,
|
||||||
@@ -928,3 +1145,66 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::functions::Function;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_iterator() {
|
||||||
|
// This checks that the number of functions in the enum is the same
|
||||||
|
// as the number of functions in the Iterator.
|
||||||
|
|
||||||
|
// This is tricky. In Rust we cannot loop over all the members of an enum.
|
||||||
|
// There are alternatives like using an external crate like strum.
|
||||||
|
// But I am not in the mood for that.
|
||||||
|
|
||||||
|
// What we do here is read this file , extract the functions in the enum
|
||||||
|
// and check they are the same as in the iterator
|
||||||
|
let file = File::open("src/functions/mod.rs").unwrap();
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let mut start = false;
|
||||||
|
let mut list = Vec::new();
|
||||||
|
|
||||||
|
for line in reader.lines() {
|
||||||
|
let text = line.unwrap();
|
||||||
|
let text = text.trim().trim_end_matches(',');
|
||||||
|
if text == "pub enum Function {" {
|
||||||
|
start = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if start {
|
||||||
|
if text == "}" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if text.starts_with("//") {
|
||||||
|
// skip comments
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if text.is_empty() {
|
||||||
|
// skip empty lines
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.push(text.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We make a list with their functions names, but we escape ".": ERROR.TYPE => ERRORTYPE
|
||||||
|
let iter_list = Function::into_iter()
|
||||||
|
.map(|f| format!("{}", f).replace('.', ""))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let len = iter_list.len();
|
||||||
|
|
||||||
|
assert_eq!(list.len(), len);
|
||||||
|
// We still need to check there are no duplicates. This will fail if a function in iter_list
|
||||||
|
// is included twice and one is missing
|
||||||
|
for function in list {
|
||||||
|
assert!(iter_list.contains(&function.to_uppercase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(first_range.left.sheet)
|
.worksheet(first_range.left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension();
|
.get_dimension();
|
||||||
let max_row = dimension.max_row;
|
let max_row = dimension.max_row;
|
||||||
let max_column = dimension.max_column;
|
let max_column = dimension.max_column;
|
||||||
|
|
||||||
@@ -530,7 +530,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(sum_range.left.sheet)
|
.worksheet(sum_range.left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if left_column == 1 && right_column == LAST_COLUMN {
|
if left_column == 1 && right_column == LAST_COLUMN {
|
||||||
@@ -538,7 +538,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(sum_range.left.sheet)
|
.worksheet(sum_range.left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -892,7 +892,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -900,7 +900,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..row2 + 1 {
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_row;
|
.max_row;
|
||||||
}
|
}
|
||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
@@ -263,7 +263,7 @@ impl Model {
|
|||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(left.sheet)
|
||||||
.expect("Sheet expected during evaluation.")
|
.expect("Sheet expected during evaluation.")
|
||||||
.dimension()
|
.get_dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
let left = CellReferenceIndex {
|
let left = CellReferenceIndex {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
use crate::model::Model;
|
|
||||||
|
|
||||||
// The gc (Garbage Collector) cleans up leftover elements in the workbook:
|
|
||||||
// * Strings that are no longe reachable
|
|
||||||
// * Styles that are no longer reachable
|
|
||||||
// * ...
|
|
||||||
|
|
||||||
impl Model {
|
|
||||||
pub(crate) fn gc(&mut self) -> Result<(), String> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
base/src/language/language.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PfrendeesD<>VRAITRUEWAHRVERDADEROTVFAUXFALSEFALSCHFALSOUw#REF!#REF!#BEZUG!#¡REF!e<>#NOM?#NAME?#NAME?#¿NOMBRE?x<>#VALEUR!#VALUE!#WERT!#¡VALOR!w<>#DIV/0!#DIV/0!#DIV/0!#¡DIV/0!<04>#N/A#N/A#NV#N/AXv#NOMBRE!#NUM!#ZAHL!#¡NUM!<02><>#N/IMPL!#N/IMPL!#N/IMPL!#N/IMPL!w{#SPILL!#SPILL!#ÜBERLAUF!#SPILL!ff#CALC!#CALC!#CALC!#CALC!ff#CIRC!#CIRC!#CIRC!#CIRC!ww#ERROR!#ERROR!#ERROR!#ERROR!ff#NULL!#NULL!#NULL!#NULL!
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
use bitcode::{Decode, Encode};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Booleans {
|
pub struct Booleans {
|
||||||
#[serde(rename = "true")]
|
pub r#true: String,
|
||||||
pub true_value: String,
|
pub r#false: String,
|
||||||
#[serde(rename = "false")]
|
|
||||||
pub false_value: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Errors {
|
pub struct Errors {
|
||||||
#[serde(rename = "ref")]
|
pub r#ref: String,
|
||||||
pub ref_value: String,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub div: String,
|
pub div: String,
|
||||||
@@ -28,14 +25,14 @@ pub struct Errors {
|
|||||||
pub null: String,
|
pub null: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
pub booleans: Booleans,
|
pub booleans: Booleans,
|
||||||
pub errors: Errors,
|
pub errors: Errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
|
||||||
serde_json::from_str(include_str!("language.json")).expect("Failed parsing language file")
|
bitcode::decode(include_bytes!("language.bin")).expect("Failed parsing language file")
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn get_language(id: &str) -> Result<&Language, String> {
|
pub fn get_language(id: &str) -> Result<&Language, String> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies]
|
//! [dependencies]
|
||||||
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
|
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc" }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
|
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
|
||||||
@@ -31,29 +31,30 @@ pub mod expressions;
|
|||||||
pub mod formatter;
|
pub mod formatter;
|
||||||
pub mod language;
|
pub mod language;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
pub mod model;
|
|
||||||
pub mod new_empty;
|
pub mod new_empty;
|
||||||
pub mod number_format;
|
pub mod number_format;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod worksheet;
|
pub mod worksheet;
|
||||||
|
|
||||||
mod functions;
|
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod cast;
|
mod cast;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod styles;
|
|
||||||
|
|
||||||
mod diffs;
|
mod diffs;
|
||||||
|
mod functions;
|
||||||
mod implicit_intersection;
|
mod implicit_intersection;
|
||||||
|
mod model;
|
||||||
|
mod styles;
|
||||||
mod units;
|
mod units;
|
||||||
|
mod user_model;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod workbook;
|
mod workbook;
|
||||||
mod garbage_collector;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod mock_time;
|
pub mod mock_time;
|
||||||
|
|
||||||
|
pub use model::get_milliseconds_since_epoch;
|
||||||
|
pub use model::Model;
|
||||||
|
pub use user_model::UserModel;
|
||||||
|
|||||||
BIN
base/src/locale/locales.bin
Normal file
@@ -1,32 +1,29 @@
|
|||||||
|
use bitcode::{Decode, Encode};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Locale {
|
pub struct Locale {
|
||||||
pub dates: Dates,
|
pub dates: Dates,
|
||||||
pub numbers: NumbersProperties,
|
pub numbers: NumbersProperties,
|
||||||
pub currency: Currency,
|
pub currency: Currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Currency {
|
pub struct Currency {
|
||||||
pub iso: String,
|
pub iso: String,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct NumbersProperties {
|
pub struct NumbersProperties {
|
||||||
#[serde(rename = "symbols-numberSystem-latn")]
|
|
||||||
pub symbols: NumbersSymbols,
|
pub symbols: NumbersSymbols,
|
||||||
#[serde(rename = "decimalFormats-numberSystem-latn")]
|
|
||||||
pub decimal_formats: DecimalFormats,
|
pub decimal_formats: DecimalFormats,
|
||||||
#[serde(rename = "currencyFormats-numberSystem-latn")]
|
|
||||||
pub currency_formats: CurrencyFormats,
|
pub currency_formats: CurrencyFormats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct Dates {
|
pub struct Dates {
|
||||||
pub day_names: Vec<String>,
|
pub day_names: Vec<String>,
|
||||||
pub day_names_short: Vec<String>,
|
pub day_names_short: Vec<String>,
|
||||||
@@ -35,8 +32,7 @@ pub struct Dates {
|
|||||||
pub months_letter: Vec<String>,
|
pub months_letter: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct NumbersSymbols {
|
pub struct NumbersSymbols {
|
||||||
pub decimal: String,
|
pub decimal: String,
|
||||||
pub group: String,
|
pub group: String,
|
||||||
@@ -54,40 +50,26 @@ pub struct NumbersSymbols {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct CurrencyFormats {
|
pub struct CurrencyFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
#[serde(rename = "standard-alphaNextToNumber")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub standard_alpha_next_to_number: Option<String>,
|
pub standard_alpha_next_to_number: Option<String>,
|
||||||
#[serde(rename = "standard-noCurrency")]
|
|
||||||
pub standard_no_currency: String,
|
pub standard_no_currency: String,
|
||||||
pub accounting: String,
|
pub accounting: String,
|
||||||
#[serde(rename = "accounting-alphaNextToNumber")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub accounting_alpha_next_to_number: Option<String>,
|
pub accounting_alpha_next_to_number: Option<String>,
|
||||||
#[serde(rename = "accounting-noCurrency")]
|
|
||||||
pub accounting_no_currency: String,
|
pub accounting_no_currency: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct DecimalFormats {
|
pub struct DecimalFormats {
|
||||||
pub standard: String,
|
pub standard: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static LOCALES: Lazy<HashMap<String, Locale>> = Lazy::new(|| {
|
static LOCALES: Lazy<HashMap<String, Locale>> =
|
||||||
serde_json::from_str(include_str!("locales.json")).expect("Failed parsing locale")
|
Lazy::new(|| bitcode::decode(include_bytes!("locales.bin")).expect("Failed parsing locale"));
|
||||||
});
|
|
||||||
|
|
||||||
pub fn get_locale(_id: &str) -> Result<&Locale, String> {
|
pub fn get_locale(id: &str) -> Result<&Locale, String> {
|
||||||
// TODO: pass the locale once we implement locales in Rust
|
// TODO: pass the locale once we implement locales in Rust
|
||||||
let locale = LOCALES.get("en").ok_or("Invalid locale")?;
|
|
||||||
Ok(locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove this function one we implement locales properly
|
|
||||||
pub fn get_locale_fix(id: &str) -> Result<&Locale, String> {
|
|
||||||
let locale = LOCALES.get(id).ok_or("Invalid locale")?;
|
let locale = LOCALES.get(id).ok_or("Invalid locale")?;
|
||||||
Ok(locale)
|
Ok(locale)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::cell::RefCell;
|
|||||||
// 8 November 2022 12:13 Berlin time
|
// 8 November 2022 12:13 Berlin time
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static MOCK_TIME: RefCell<i64> = RefCell::new(1667906008578);
|
static MOCK_TIME: RefCell<i64> = const { RefCell::new(1667906008578) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -20,3 +20,18 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
pub fn set_mock_time(time: i64) {
|
pub fn set_mock_time(time: i64) {
|
||||||
MOCK_TIME.with(|cell| *cell.borrow_mut() = time);
|
MOCK_TIME.with(|cell| *cell.borrow_mut() = time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::mock_time::MOCK_TIME;
|
||||||
|
|
||||||
|
use super::get_milliseconds_since_epoch;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mock_time() {
|
||||||
|
let t = get_milliseconds_since_epoch();
|
||||||
|
assert_eq!(t, 1667906008578);
|
||||||
|
|
||||||
|
MOCK_TIME.with_borrow(|v| assert_eq!(*v, 1667906008578));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
//! # Model
|
|
||||||
//!
|
|
||||||
//! Note that sheets are 0-indexed and rows and columns are 1-indexed.
|
|
||||||
//!
|
|
||||||
//! IronCalc is row first. A cell is referenced by (`sheet`, `row`, `column`)
|
|
||||||
//!
|
|
||||||
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
@@ -47,7 +38,11 @@ use chrono_tz::Tz;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use crate::mock_time::get_milliseconds_since_epoch;
|
pub use crate::mock_time::get_milliseconds_since_epoch;
|
||||||
|
|
||||||
/// wasm implementation for time
|
/// Number of milliseconds since January 1, 1970
|
||||||
|
/// Used by time and date functions. It takes the value from the environment:
|
||||||
|
/// * The Operative System
|
||||||
|
/// * The JavaScript environment
|
||||||
|
/// * Or mocked for tests
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -58,6 +53,11 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
.as_millis() as i64
|
.as_millis() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of milliseconds since January 1, 1970
|
||||||
|
/// Used by time and date functions. It takes the value from the environment:
|
||||||
|
/// * The Operative System
|
||||||
|
/// * The JavaScript environment
|
||||||
|
/// * Or mocked for tests
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -67,7 +67,7 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
|
|
||||||
/// A cell might be evaluated or being evaluated
|
/// A cell might be evaluated or being evaluated
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum CellState {
|
pub(crate) enum CellState {
|
||||||
/// The cell has already been evaluated
|
/// The cell has already been evaluated
|
||||||
Evaluated,
|
Evaluated,
|
||||||
/// The cell is being evaluated
|
/// The cell is being evaluated
|
||||||
@@ -75,7 +75,7 @@ pub enum CellState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A parsed formula for a defined name
|
/// A parsed formula for a defined name
|
||||||
pub enum ParsedDefinedName {
|
pub(crate) enum ParsedDefinedName {
|
||||||
/// CellReference (`=C4`)
|
/// CellReference (`=C4`)
|
||||||
CellReference(CellReferenceIndex),
|
CellReference(CellReferenceIndex),
|
||||||
/// A Range (`=C4:D6`)
|
/// A Range (`=C4:D6`)
|
||||||
@@ -105,19 +105,19 @@ pub struct Model {
|
|||||||
/// A list of parsed formulas
|
/// A list of parsed formulas
|
||||||
pub parsed_formulas: Vec<Vec<Node>>,
|
pub parsed_formulas: Vec<Vec<Node>>,
|
||||||
/// A list of parsed defined names
|
/// A list of parsed defined names
|
||||||
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
pub(crate) parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
||||||
/// An optimization to lookup strings faster
|
/// An optimization to lookup strings faster
|
||||||
pub shared_strings: HashMap<String, usize>,
|
pub(crate) shared_strings: HashMap<String, usize>,
|
||||||
/// An instance of the parser
|
/// An instance of the parser
|
||||||
pub parser: Parser,
|
pub(crate) parser: Parser,
|
||||||
/// The list of cells with formulas that are evaluated of being evaluated
|
/// The list of cells with formulas that are evaluated of being evaluated
|
||||||
pub cells: HashMap<(u32, i32, i32), CellState>,
|
pub(crate) cells: HashMap<(u32, i32, i32), CellState>,
|
||||||
/// The locale of the model
|
/// The locale of the model
|
||||||
pub locale: Locale,
|
pub(crate) locale: Locale,
|
||||||
/// Tha language used
|
/// Tha language used
|
||||||
pub language: Language,
|
pub(crate) language: Language,
|
||||||
/// The timezone used to evaluate the model
|
/// The timezone used to evaluate the model
|
||||||
pub tz: Tz,
|
pub(crate) tz: Tz,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Maybe this should be the same as CellReference
|
// FIXME: Maybe this should be the same as CellReference
|
||||||
@@ -659,7 +659,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
|
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
|
||||||
@@ -726,7 +726,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
|
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
|
||||||
@@ -798,17 +798,17 @@ impl Model {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a model from a String representation of a workbook
|
/// Returns a model from an internal binary representation of a workbook
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
||||||
/// let model2 = Model::from_json(&model.to_json_str())?;
|
/// let model2 = Model::from_bytes(&model.to_bytes())?;
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// model2.get_cell_value_by_index(0, 1, 1),
|
/// model2.get_cell_value_by_index(0, 1, 1),
|
||||||
/// Ok(CellValue::String("Stella!".to_string()))
|
/// Ok(CellValue::String("Stella!".to_string()))
|
||||||
@@ -816,9 +816,12 @@ impl Model {
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_json(s: &str) -> Result<Model, String> {
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Model::to_bytes]
|
||||||
|
pub fn from_bytes(s: &[u8]) -> Result<Model, String> {
|
||||||
let workbook: Workbook =
|
let workbook: Workbook =
|
||||||
serde_json::from_str(s).map_err(|_| "Error parsing workbook".to_string())?;
|
bitcode::decode(s).map_err(|e| format!("Error parsing workbook: {e}"))?;
|
||||||
Model::from_workbook(workbook)
|
Model::from_workbook(workbook)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +830,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -896,7 +899,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -965,7 +968,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
|
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1036,7 +1039,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1083,7 +1086,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1138,13 +1141,13 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
|
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// let result = model.cell_formula(sheet, row, column)?;
|
/// let result = model.get_cell_formula(sheet, row, column)?;
|
||||||
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
|
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -1152,24 +1155,33 @@ impl Model {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [Model::get_cell_content()]
|
/// * [Model::get_cell_content()]
|
||||||
pub fn cell_formula(
|
pub fn get_cell_formula(
|
||||||
&self,
|
&self,
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
row: i32,
|
row: i32,
|
||||||
column: i32,
|
column: i32,
|
||||||
) -> Result<Option<String>, String> {
|
) -> Result<Option<String>, String> {
|
||||||
let worksheet = self.workbook.worksheet(sheet)?;
|
let worksheet = self.workbook.worksheet(sheet)?;
|
||||||
Ok(worksheet.cell(row, column).and_then(|cell| {
|
match worksheet.cell(row, column) {
|
||||||
cell.get_formula().map(|formula_index| {
|
Some(cell) => match cell.get_formula() {
|
||||||
let formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
|
Some(formula_index) => {
|
||||||
let cell_ref = CellReferenceRC {
|
let formula = &self
|
||||||
sheet: worksheet.get_name(),
|
.parsed_formulas
|
||||||
row,
|
.get(sheet as usize)
|
||||||
column,
|
.ok_or("missing sheet")?
|
||||||
};
|
.get(formula_index as usize)
|
||||||
format!("={}", to_string(formula, &cell_ref))
|
.ok_or("missing formula")?;
|
||||||
})
|
let cell_ref = CellReferenceRC {
|
||||||
}))
|
sheet: worksheet.get_name(),
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
};
|
||||||
|
Ok(Some(format!("={}", to_string(formula, &cell_ref))))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
},
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of a cell with some text
|
/// Updates the value of a cell with some text
|
||||||
@@ -1178,7 +1190,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1221,7 +1233,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1258,7 +1270,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1296,7 +1308,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
@@ -1348,7 +1360,7 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # use ironcalc_base::cell::CellValue;
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
@@ -1358,7 +1370,7 @@ impl Model {
|
|||||||
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
|
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
|
||||||
/// assert_eq!(model.formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
|
/// assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -1529,7 +1541,7 @@ impl Model {
|
|||||||
/// Returns the cell value for (`sheet`, `row`, `column`)
|
/// Returns the cell value for (`sheet`, `row`, `column`)
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [Model::formatted_cell_value()]
|
/// * [Model::get_formatted_cell_value()]
|
||||||
pub fn get_cell_value_by_index(
|
pub fn get_cell_value_by_index(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
@@ -1555,35 +1567,42 @@ impl Model {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use ironcalc_base::model::Model;
|
/// # use ironcalc_base::Model;
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
/// let (sheet, row, column) = (0, 1, 1);
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
|
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
|
||||||
/// model.evaluate();
|
/// model.evaluate();
|
||||||
/// let result = model.formatted_cell_value(sheet, row, column)?;
|
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
||||||
/// assert_eq!(result, "0.333333333".to_string());
|
/// assert_eq!(result, "0.333333333".to_string());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn formatted_cell_value(
|
pub fn get_formatted_cell_value(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
row: i32,
|
row: i32,
|
||||||
column: i32,
|
column: i32,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
|
match self.workbook.worksheet(sheet_index)?.cell(row, column) {
|
||||||
let cell = self
|
Some(cell) => {
|
||||||
.workbook
|
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
|
||||||
.worksheet(sheet_index)?
|
let formatted_value =
|
||||||
.cell(row, column)
|
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
|
||||||
.cloned()
|
format_number(value, &format, &self.locale).text
|
||||||
.unwrap_or_default();
|
});
|
||||||
let formatted_value =
|
Ok(formatted_value)
|
||||||
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
|
}
|
||||||
format_number(value, &format, &self.locale).text
|
None => Ok("".to_string()),
|
||||||
});
|
}
|
||||||
Ok(formatted_value)
|
}
|
||||||
|
|
||||||
|
/// Return the typeof a cell
|
||||||
|
pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<CellType, String> {
|
||||||
|
Ok(match self.workbook.worksheet(sheet)?.cell(row, column) {
|
||||||
|
Some(c) => c.get_type(),
|
||||||
|
None => CellType::Number,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string with the cell content. If there is a formula returns the formula
|
/// Returns a string with the cell content. If there is a formula returns the formula
|
||||||
@@ -1647,15 +1666,53 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets cell to empty. Can be used to delete value without affecting style.
|
/// Removes the content of the cell but leaves the style.
|
||||||
pub fn set_cell_empty(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
///
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
/// See also:
|
||||||
worksheet.set_cell_empty(row, column);
|
/// * [Model::cell_clear_all()]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
|
/// model.set_user_input(sheet, row, column, "100$".to_string());
|
||||||
|
/// model.cell_clear_contents(sheet, row, column);
|
||||||
|
/// model.set_user_input(sheet, row, column, "10".to_string());
|
||||||
|
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
||||||
|
/// assert_eq!(result, "10$".to_string());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn cell_clear_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
||||||
|
self.workbook
|
||||||
|
.worksheet_mut(sheet)?
|
||||||
|
.cell_clear_contents(row, column);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a cell by removing it from worksheet data.
|
/// Deletes a cell by removing it from worksheet data. All content and style is removed.
|
||||||
pub fn delete_cell(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Model::cell_clear_contents()]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
|
/// model.set_user_input(sheet, row, column, "100$".to_string());
|
||||||
|
/// model.cell_clear_all(sheet, row, column);
|
||||||
|
/// model.set_user_input(sheet, row, column, "10".to_string());
|
||||||
|
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
|
||||||
|
/// assert_eq!(result, "10".to_string());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
pub fn cell_clear_all(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||||
|
|
||||||
let sheet_data = &mut worksheet.sheet_data;
|
let sheet_data = &mut worksheet.sheet_data;
|
||||||
@@ -1682,9 +1739,8 @@ impl Model {
|
|||||||
if r.r == row {
|
if r.r == row {
|
||||||
if r.custom_format {
|
if r.custom_format {
|
||||||
return r.s;
|
return r.s;
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cols = &self.workbook.worksheets[sheet as usize].cols;
|
let cols = &self.workbook.worksheets[sheet as usize].cols;
|
||||||
@@ -1707,21 +1763,32 @@ impl Model {
|
|||||||
.get_style(self.get_cell_style_index(sheet, row, column))
|
.get_style(self.get_cell_style_index(sheet, row, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a JSON string of the workbook
|
/// Returns an internal binary representation of the workbook
|
||||||
pub fn to_json_str(&self) -> String {
|
///
|
||||||
match serde_json::to_string(&self.workbook) {
|
/// See also:
|
||||||
Ok(s) => s,
|
/// * [Model::from_bytes]
|
||||||
Err(_) => {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
// TODO, is this branch possible at all?
|
bitcode::encode(&self.workbook)
|
||||||
json!({"error": "Error stringifying workbook"}).to_string()
|
}
|
||||||
}
|
|
||||||
}
|
/// Returns data about the worksheets
|
||||||
|
pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
|
||||||
|
self.workbook
|
||||||
|
.worksheets
|
||||||
|
.iter()
|
||||||
|
.map(|worksheet| SheetProperties {
|
||||||
|
name: worksheet.get_name(),
|
||||||
|
state: worksheet.state.to_string(),
|
||||||
|
color: worksheet.color.clone(),
|
||||||
|
sheet_id: worksheet.sheet_id,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns markup representation of the given `sheet`.
|
/// Returns markup representation of the given `sheet`.
|
||||||
pub fn sheet_markup(&self, sheet: u32) -> Result<String, String> {
|
pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
|
||||||
let worksheet = self.workbook.worksheet(sheet)?;
|
let worksheet = self.workbook.worksheet(sheet)?;
|
||||||
let dimension = worksheet.dimension();
|
let dimension = worksheet.get_dimension();
|
||||||
|
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
|
|
||||||
@@ -1729,9 +1796,9 @@ impl Model {
|
|||||||
let mut row_markup: Vec<String> = Vec::new();
|
let mut row_markup: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for column in 1..(dimension.max_column + 1) {
|
for column in 1..(dimension.max_column + 1) {
|
||||||
let mut cell_markup = match self.cell_formula(sheet, row, column)? {
|
let mut cell_markup = match self.get_cell_formula(sheet, row, column)? {
|
||||||
Some(formula) => formula,
|
Some(formula) => formula,
|
||||||
None => self.formatted_cell_value(sheet, row, column)?,
|
None => self.get_formatted_cell_value(sheet, row, column)?,
|
||||||
};
|
};
|
||||||
let style = self.get_style_for_cell(sheet, row, column);
|
let style = self.get_style_for_cell(sheet, row, column);
|
||||||
if style.font.b {
|
if style.font.b {
|
||||||
@@ -1770,7 +1837,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of frozen rows in `sheet`
|
/// Returns the number of frozen rows in `sheet`
|
||||||
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_rows)
|
Ok(worksheet.frozen_rows)
|
||||||
} else {
|
} else {
|
||||||
@@ -1779,7 +1846,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the number of frozen columns in `sheet`
|
/// Return the number of frozen columns in `sheet`
|
||||||
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_columns)
|
Ok(worksheet.frozen_columns)
|
||||||
} else {
|
} else {
|
||||||
@@ -1820,6 +1887,34 @@ impl Model {
|
|||||||
Err("Invalid sheet".to_string())
|
Err("Invalid sheet".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the width of a column
|
||||||
|
#[inline]
|
||||||
|
pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
|
||||||
|
self.workbook.worksheet(sheet)?.get_column_width(column)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the width of a column
|
||||||
|
#[inline]
|
||||||
|
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), String> {
|
||||||
|
self.workbook
|
||||||
|
.worksheet_mut(sheet)?
|
||||||
|
.set_column_width(column, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the height of a row
|
||||||
|
#[inline]
|
||||||
|
pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
|
||||||
|
self.workbook.worksheet(sheet)?.row_height(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the height of a row
|
||||||
|
#[inline]
|
||||||
|
pub fn set_row_height(&mut self, sheet: u32, column: i32, height: f64) -> Result<(), String> {
|
||||||
|
self.workbook
|
||||||
|
.worksheet_mut(sheet)?
|
||||||
|
.set_row_height(column, height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::DateTime;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -6,14 +6,16 @@ use crate::{
|
|||||||
calc_result::Range,
|
calc_result::Range,
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::LexerMode,
|
lexer::LexerMode,
|
||||||
parser::stringify::{rename_sheet_in_node, to_rc_format},
|
parser::{
|
||||||
parser::Parser,
|
stringify::{rename_sheet_in_node, to_rc_format},
|
||||||
|
Parser,
|
||||||
|
},
|
||||||
types::CellReferenceRC,
|
types::CellReferenceRC,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale,
|
locale::get_locale,
|
||||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||||
types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
|
types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||||
utils::ParsedReference,
|
utils::ParsedReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ impl Model {
|
|||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
frozen_columns: 0,
|
frozen_columns: 0,
|
||||||
frozen_rows: 0,
|
frozen_rows: 0,
|
||||||
|
selection: Selection {
|
||||||
|
is_selected: false,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +131,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reparses all formulas and defined names
|
// Reparses all formulas and defined names
|
||||||
fn reset_parsed_structures(&mut self) {
|
pub(crate) fn reset_parsed_structures(&mut self) {
|
||||||
self.parser
|
self.parser
|
||||||
.set_worksheets(self.workbook.get_worksheet_names());
|
.set_worksheets(self.workbook.get_worksheet_names());
|
||||||
self.parsed_formulas = vec![];
|
self.parsed_formulas = vec![];
|
||||||
@@ -134,10 +142,10 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a sheet with a automatically generated name
|
/// Adds a sheet with a automatically generated name
|
||||||
pub fn new_sheet(&mut self) {
|
pub fn new_sheet(&mut self) -> (String, u32) {
|
||||||
// First we find a name
|
// First we find a name
|
||||||
|
|
||||||
// TODO: When/if we support i18n the name could depend on the locale
|
// TODO: The name should depend on the locale
|
||||||
let base_name = "Sheet";
|
let base_name = "Sheet";
|
||||||
let base_name_uppercase = base_name.to_uppercase();
|
let base_name_uppercase = base_name.to_uppercase();
|
||||||
let mut index = 1;
|
let mut index = 1;
|
||||||
@@ -156,6 +164,7 @@ impl Model {
|
|||||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
||||||
self.workbook.worksheets.push(worksheet);
|
self.workbook.worksheets.push(worksheet);
|
||||||
self.reset_parsed_structures();
|
self.reset_parsed_structures();
|
||||||
|
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a sheet with a particular index
|
/// Inserts a sheet with a particular index
|
||||||
@@ -223,10 +232,10 @@ impl Model {
|
|||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !is_valid_sheet_name(new_name) {
|
if !is_valid_sheet_name(new_name) {
|
||||||
return Err(format!("Invalid name for a sheet: '{}'", new_name));
|
return Err(format!("Invalid name for a sheet: '{}'.", new_name));
|
||||||
}
|
}
|
||||||
if self.get_sheet_index_by_name(new_name).is_some() {
|
if self.get_sheet_index_by_name(new_name).is_some() {
|
||||||
return Err(format!("Sheet already exists: '{}'", new_name));
|
return Err(format!("Sheet already exists: '{}'.", new_name));
|
||||||
}
|
}
|
||||||
let worksheets = &self.workbook.worksheets;
|
let worksheets = &self.workbook.worksheets;
|
||||||
let sheet_count = worksheets.len() as u32;
|
let sheet_count = worksheets.len() as u32;
|
||||||
@@ -270,7 +279,7 @@ impl Model {
|
|||||||
if sheet_count == 1 {
|
if sheet_count == 1 {
|
||||||
return Err("Cannot delete only sheet".to_string());
|
return Err("Cannot delete only sheet".to_string());
|
||||||
};
|
};
|
||||||
if sheet_index > sheet_count {
|
if sheet_index >= sheet_count {
|
||||||
return Err("Sheet index too large".to_string());
|
return Err("Sheet index too large".to_string());
|
||||||
}
|
}
|
||||||
self.workbook.worksheets.remove(sheet_index as usize);
|
self.workbook.worksheets.remove(sheet_index as usize);
|
||||||
@@ -323,7 +332,7 @@ impl Model {
|
|||||||
|
|
||||||
let milliseconds = get_milliseconds_since_epoch();
|
let milliseconds = get_milliseconds_since_epoch();
|
||||||
let seconds = milliseconds / 1000;
|
let seconds = milliseconds / 1000;
|
||||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
let dt = match DateTime::from_timestamp(seconds, 0) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
|
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
|
||||||
};
|
};
|
||||||
|
|||||||
18
base/src/test/functions/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
mod test_fn_average;
|
||||||
|
mod test_fn_averageifs;
|
||||||
|
mod test_fn_choose;
|
||||||
|
mod test_fn_concatenate;
|
||||||
|
mod test_fn_count;
|
||||||
|
mod test_fn_exact;
|
||||||
|
mod test_fn_financial;
|
||||||
|
mod test_fn_if;
|
||||||
|
mod test_fn_maxifs;
|
||||||
|
mod test_fn_minifs;
|
||||||
|
mod test_fn_offset;
|
||||||
|
mod test_fn_product;
|
||||||
|
mod test_fn_rept;
|
||||||
|
mod test_fn_sum;
|
||||||
|
mod test_fn_sumifs;
|
||||||
|
mod test_fn_textbefore;
|
||||||
|
mod test_fn_textjoin;
|
||||||
|
mod test_fn_type;
|
||||||
@@ -1,56 +1,39 @@
|
|||||||
|
mod engineering;
|
||||||
|
mod functions;
|
||||||
mod test_actions;
|
mod test_actions;
|
||||||
mod test_binary_search;
|
mod test_binary_search;
|
||||||
mod test_cell;
|
mod test_cell;
|
||||||
|
mod test_cell_clear_contents;
|
||||||
mod test_circular_references;
|
mod test_circular_references;
|
||||||
mod test_column_width;
|
mod test_column_width;
|
||||||
mod test_criteria;
|
mod test_criteria;
|
||||||
mod test_currency;
|
mod test_currency;
|
||||||
mod test_date_and_time;
|
mod test_date_and_time;
|
||||||
mod test_error_propagation;
|
mod test_error_propagation;
|
||||||
mod test_fn_average;
|
mod test_escape_quotes;
|
||||||
mod test_fn_averageifs;
|
mod test_extend;
|
||||||
mod test_fn_choose;
|
|
||||||
mod test_fn_concatenate;
|
|
||||||
mod test_fn_count;
|
|
||||||
mod test_fn_exact;
|
|
||||||
mod test_fn_financial;
|
|
||||||
mod test_fn_if;
|
|
||||||
mod test_fn_maxifs;
|
|
||||||
mod test_fn_minifs;
|
|
||||||
mod test_fn_product;
|
|
||||||
mod test_fn_rept;
|
|
||||||
mod test_fn_sum;
|
|
||||||
mod test_fn_sumifs;
|
|
||||||
mod test_fn_textbefore;
|
|
||||||
mod test_fn_textjoin;
|
|
||||||
mod test_forward_references;
|
mod test_forward_references;
|
||||||
|
mod test_frozen_rows_and_columns;
|
||||||
mod test_frozen_rows_columns;
|
mod test_frozen_rows_columns;
|
||||||
mod test_general;
|
mod test_general;
|
||||||
|
mod test_get_cell_content;
|
||||||
mod test_math;
|
mod test_math;
|
||||||
mod test_metadata;
|
mod test_metadata;
|
||||||
mod test_model_delete_cell;
|
mod test_model_cell_clear_all;
|
||||||
mod test_model_is_empty_cell;
|
mod test_model_is_empty_cell;
|
||||||
mod test_model_set_cell_empty;
|
|
||||||
mod test_move_formula;
|
mod test_move_formula;
|
||||||
|
mod test_number_format;
|
||||||
|
mod test_percentage;
|
||||||
mod test_quote_prefix;
|
mod test_quote_prefix;
|
||||||
mod test_set_user_input;
|
mod test_set_user_input;
|
||||||
mod test_sheet_markup;
|
mod test_sheet_markup;
|
||||||
mod test_sheets;
|
mod test_sheets;
|
||||||
|
mod test_shift_cells;
|
||||||
mod test_styles;
|
mod test_styles;
|
||||||
|
mod test_today;
|
||||||
mod test_trigonometric;
|
mod test_trigonometric;
|
||||||
|
mod test_types;
|
||||||
mod test_workbook;
|
mod test_workbook;
|
||||||
mod test_worksheet;
|
mod test_worksheet;
|
||||||
|
mod user_model;
|
||||||
pub(crate) mod util;
|
pub(crate) mod util;
|
||||||
|
|
||||||
mod engineering;
|
|
||||||
mod test_fn_offset;
|
|
||||||
mod test_number_format;
|
|
||||||
|
|
||||||
mod test_escape_quotes;
|
|
||||||
mod test_extend;
|
|
||||||
mod test_fn_type;
|
|
||||||
mod test_frozen_rows_and_columns;
|
|
||||||
mod test_get_cell_content;
|
|
||||||
mod test_percentage;
|
|
||||||
mod test_today;
|
|
||||||
mod test_gc;
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::constants::LAST_COLUMN;
|
use crate::constants::LAST_COLUMN;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
use crate::types::Col;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_columns() {
|
fn test_insert_columns() {
|
||||||
@@ -195,6 +196,250 @@ fn test_delete_columns() {
|
|||||||
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
|
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_column_width() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, column) = (0, 5);
|
||||||
|
let normal_width = model.get_column_width(sheet, column).unwrap();
|
||||||
|
// Set the width of one column to 5 times the normal width
|
||||||
|
assert!(model
|
||||||
|
.set_column_width(sheet, column, normal_width * 5.0)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
// delete it
|
||||||
|
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
||||||
|
|
||||||
|
// all the columns around have the expected width
|
||||||
|
assert_eq!(
|
||||||
|
model.get_column_width(sheet, column - 1).unwrap(),
|
||||||
|
normal_width
|
||||||
|
);
|
||||||
|
assert_eq!(model.get_column_width(sheet, column).unwrap(), normal_width);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_column_width(sheet, column + 1).unwrap(),
|
||||||
|
normal_width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// We set the style of columns 4 to 7 and delete column 4
|
||||||
|
// We check that columns 4 to 6 have the new style
|
||||||
|
fn test_delete_first_column_width() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 4,
|
||||||
|
max: 7,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 4);
|
||||||
|
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 4,
|
||||||
|
max: 6,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Delete the last column in the range
|
||||||
|
fn test_delete_last_column_width() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 4,
|
||||||
|
max: 7,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 7);
|
||||||
|
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 4,
|
||||||
|
max: 6,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Deletes columns at the end
|
||||||
|
fn test_delete_last_few_columns_width() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 4,
|
||||||
|
max: 17,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 13);
|
||||||
|
assert!(model.delete_columns(sheet, column, 10).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 4,
|
||||||
|
max: 12,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_columns_non_overlapping_left() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 10,
|
||||||
|
max: 17,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 3);
|
||||||
|
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 6,
|
||||||
|
max: 13,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_columns_overlapping_left() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 10,
|
||||||
|
max: 20,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 8);
|
||||||
|
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 8,
|
||||||
|
max: 16,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_columns_non_overlapping_right() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 10,
|
||||||
|
max: 17,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
let (sheet, column) = (0, 23);
|
||||||
|
assert!(model.delete_columns(sheet, column, 4).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 10,
|
||||||
|
max: 17,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// deletes some columns in the middle of the range
|
||||||
|
fn test_delete_middle_column_width() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
// styled columns [4, 17]
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 4,
|
||||||
|
max: 17,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
|
||||||
|
// deletes columns 10, 11, 12
|
||||||
|
let (sheet, column) = (0, 10);
|
||||||
|
assert!(model.delete_columns(sheet, column, 3).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
cols[0],
|
||||||
|
Col {
|
||||||
|
min: 4,
|
||||||
|
max: 14,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// the range is inside the deleted columns
|
||||||
|
fn delete_range_in_columns() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
// styled columns [6, 10]
|
||||||
|
model.workbook.worksheets[0].cols = vec![Col {
|
||||||
|
min: 6,
|
||||||
|
max: 10,
|
||||||
|
width: 300.0,
|
||||||
|
custom_width: true,
|
||||||
|
style: None,
|
||||||
|
}];
|
||||||
|
|
||||||
|
// deletes columns [4, 17]
|
||||||
|
let (sheet, column) = (0, 4);
|
||||||
|
assert!(model.delete_columns(sheet, column, 8).is_ok());
|
||||||
|
let cols = &model.workbook.worksheets[0].cols;
|
||||||
|
assert_eq!(cols.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_columns_error() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, column) = (0, 5);
|
||||||
|
assert!(model.delete_columns(sheet, column, -1).is_err());
|
||||||
|
assert!(model.delete_columns(sheet, column, 0).is_err());
|
||||||
|
assert!(model.delete_columns(sheet, column, 1).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_rows() {
|
fn test_delete_rows() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_cell_empty_non_existing_sheet() {
|
fn test_cell_clear_contents_non_existing_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.set_cell_empty(13, 1, 1),
|
model.cell_clear_contents(13, 1, 1),
|
||||||
Err("Invalid sheet index".to_string())
|
Err("Invalid sheet index".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_cell_empty_unset_cell() {
|
fn test_cell_clear_contents_unset_cell() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model.set_cell_empty(0, 1, 1).unwrap();
|
model.cell_clear_contents(0, 1, 1).unwrap();
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_cell_empty_with_value() {
|
fn test_cell_clear_contents_with_value() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "hello");
|
model._set("A1", "hello");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
@@ -28,7 +28,7 @@ fn test_set_cell_empty_with_value() {
|
|||||||
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
|
|
||||||
model.set_cell_empty(0, 1, 1).unwrap();
|
model.cell_clear_contents(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -36,7 +36,7 @@ fn test_set_cell_empty_with_value() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_cell_empty_referenced_elsewhere() {
|
fn test_cell_clear_contents_referenced_elsewhere() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "35");
|
model._set("A1", "35");
|
||||||
model._set("A2", "=2*A1");
|
model._set("A2", "=2*A1");
|
||||||
@@ -47,7 +47,7 @@ fn test_set_cell_empty_referenced_elsewhere() {
|
|||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
||||||
|
|
||||||
model.set_cell_empty(0, 1, 1).unwrap();
|
model.cell_clear_contents(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -23,9 +23,9 @@ fn test_column_width() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!((worksheet.column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.get_column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.get_column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).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), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +48,11 @@ fn test_column_width_lower_edge() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!((worksheet.column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
assert!((worksheet.get_column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.get_column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON);
|
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), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +76,9 @@ fn test_column_width_higher_edge() {
|
|||||||
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
|
||||||
let worksheet = model.workbook.worksheet(0).unwrap();
|
let worksheet = model.workbook.worksheet(0).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
(worksheet.column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
|
(worksheet.get_column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
|
||||||
);
|
);
|
||||||
assert!((worksheet.column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
|
assert!((worksheet.get_column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
|
||||||
assert!((worksheet.column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).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), 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,34 +8,37 @@ use crate::{
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_empty_model() {
|
fn test_empty_model() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(model.get_frozen_rows(0), Ok(0));
|
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
|
||||||
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
||||||
|
|
||||||
let e = model.set_frozen_rows(0, 3);
|
let e = model.set_frozen_rows(0, 3);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows(0), Ok(3));
|
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
|
||||||
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
||||||
|
|
||||||
let e = model.set_frozen_columns(0, 53);
|
let e = model.set_frozen_columns(0, 53);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows(0), Ok(3));
|
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
|
||||||
assert_eq!(model.get_frozen_columns(0), Ok(53));
|
assert_eq!(model.get_frozen_columns_count(0), Ok(53));
|
||||||
|
|
||||||
// Set them back to zero
|
// Set them back to zero
|
||||||
let e = model.set_frozen_rows(0, 0);
|
let e = model.set_frozen_rows(0, 0);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
let e = model.set_frozen_columns(0, 0);
|
let e = model.set_frozen_columns(0, 0);
|
||||||
assert!(e.is_ok());
|
assert!(e.is_ok());
|
||||||
assert_eq!(model.get_frozen_rows(0), Ok(0));
|
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
|
||||||
assert_eq!(model.get_frozen_columns(0), Ok(0));
|
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_sheet() {
|
fn test_invalid_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(model.get_frozen_rows(1), Err("Invalid sheet".to_string()));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.get_frozen_columns(3),
|
model.get_frozen_rows_count(1),
|
||||||
|
Err("Invalid sheet".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_frozen_columns_count(3),
|
||||||
Err("Invalid sheet".to_string())
|
Err("Invalid sheet".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_model() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// set a string
|
|
||||||
model._set("A1", "Hello");
|
|
||||||
assert_eq!(model.shared_strings.len(), 1);
|
|
||||||
// calling the gc doesn't do anything
|
|
||||||
model.gc().unwrap();
|
|
||||||
assert_eq!(model.shared_strings.len(), 1);
|
|
||||||
|
|
||||||
// If we delete the cell the string is still in the list
|
|
||||||
model.delete_cell(0, 1, 1).unwrap();
|
|
||||||
assert_eq!(model.shared_strings.len(), 1);
|
|
||||||
|
|
||||||
// after the gc the string is no longer present
|
|
||||||
model.gc().unwrap();
|
|
||||||
assert_eq!(model.shared_strings.len(), 0);
|
|
||||||
}
|
|
||||||
@@ -411,11 +411,11 @@ fn test_get_formatted_cell_value() {
|
|||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model.formatted_cell_value(0, 1, 1).unwrap(), "foobar");
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "foobar");
|
||||||
assert_eq!(model.formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
|
assert_eq!(model.get_formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
|
||||||
assert_eq!(model.formatted_cell_value(0, 3, 1).unwrap(), "");
|
assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "");
|
||||||
assert_eq!(model.formatted_cell_value(0, 4, 1).unwrap(), "123.456");
|
assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "123.456");
|
||||||
assert_eq!(model.formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
|
assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -426,20 +426,20 @@ fn test_cell_formula() {
|
|||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 1, 1), // A1
|
model.get_cell_formula(0, 1, 1), // A1
|
||||||
Ok(Some("=1+2+3".to_string())),
|
Ok(Some("=1+2+3".to_string())),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 2, 1), // A2
|
model.get_cell_formula(0, 2, 1), // A2
|
||||||
Ok(None),
|
Ok(None),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 3, 1), // A3 - empty cell
|
model.get_cell_formula(0, 3, 1), // A3 - empty cell
|
||||||
Ok(None),
|
Ok(None),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(42, 1, 1),
|
model.get_cell_formula(42, 1, 1),
|
||||||
Err("Invalid sheet index".to_string()),
|
Err("Invalid sheet index".to_string()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -453,16 +453,16 @@ fn test_xlfn() {
|
|||||||
model.evaluate();
|
model.evaluate();
|
||||||
// Only modern formulas strip the '_xlfn.'
|
// Only modern formulas strip the '_xlfn.'
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 1, 1).unwrap(),
|
model.get_cell_formula(0, 1, 1).unwrap(),
|
||||||
Some("=_xlfn.SIN(1)".to_string())
|
Some("=_xlfn.SIN(1)".to_string())
|
||||||
);
|
);
|
||||||
// unknown formulas keep the '_xlfn.' prefix
|
// unknown formulas keep the '_xlfn.' prefix
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 2, 1).unwrap(),
|
model.get_cell_formula(0, 2, 1).unwrap(),
|
||||||
Some("=_xlfn.SINY(1)".to_string())
|
Some("=_xlfn.SINY(1)".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 3, 1).unwrap(),
|
model.get_cell_formula(0, 3, 1).unwrap(),
|
||||||
Some("=CONCAT(3,4)".to_string())
|
Some("=CONCAT(3,4)".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -474,11 +474,11 @@ fn test_letter_case() {
|
|||||||
model._set("A2", "=sIn(2)");
|
model._set("A2", "=sIn(2)");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 1, 1).unwrap(),
|
model.get_cell_formula(0, 1, 1).unwrap(),
|
||||||
Some("=SIN(1)".to_string())
|
Some("=SIN(1)".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.cell_formula(0, 2, 1).unwrap(),
|
model.get_cell_formula(0, 2, 1).unwrap(),
|
||||||
Some("=SIN(2)".to_string())
|
Some("=SIN(2)".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_cell_non_existing_sheet() {
|
fn test_cell_clear_all_non_existing_sheet() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.delete_cell(13, 1, 1),
|
model.cell_clear_all(13, 1, 1),
|
||||||
Err("Invalid sheet index".to_string())
|
Err("Invalid sheet index".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_cell_unset_cell() {
|
fn test_cell_clear_all_unset_cell() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert!(model.delete_cell(0, 1, 1).is_ok());
|
assert!(model.cell_clear_all(0, 1, 1).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_cell_with_value() {
|
fn test_cell_clear_all_with_value() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "hello");
|
model._set("A1", "hello");
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
@@ -25,7 +25,7 @@ fn test_delete_cell_with_value() {
|
|||||||
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
assert_eq!(model._get_text_at(0, 1, 1), "hello");
|
||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
|
|
||||||
model.delete_cell(0, 1, 1).unwrap();
|
model.cell_clear_all(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -33,7 +33,7 @@ fn test_delete_cell_with_value() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_cell_referenced_elsewhere() {
|
fn test_cell_clear_all_referenced_elsewhere() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", "35");
|
model._set("A1", "35");
|
||||||
model._set("A2", "=2*A1");
|
model._set("A2", "=2*A1");
|
||||||
@@ -44,7 +44,7 @@ fn test_delete_cell_referenced_elsewhere() {
|
|||||||
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
|
||||||
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
|
||||||
|
|
||||||
model.delete_cell(0, 1, 1).unwrap();
|
model.cell_clear_all(0, 1, 1).unwrap();
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text_at(0, 1, 1), "");
|
assert_eq!(model._get_text_at(0, 1, 1), "");
|
||||||
@@ -16,7 +16,7 @@ fn test_is_empty_cell() {
|
|||||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
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());
|
||||||
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
|
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
|
||||||
model.set_cell_empty(0, 3, 1).unwrap();
|
model.cell_clear_contents(0, 3, 1).unwrap();
|
||||||
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
assert!(model.is_empty_cell(0, 3, 1).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fn test_sheet_markup() {
|
|||||||
model.set_cell_style(0, 4, 1, &style).unwrap();
|
model.set_cell_style(0, 4, 1, &style).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.sheet_markup(0),
|
model.get_sheet_markup(0),
|
||||||
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
|
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,3 +236,11 @@ fn test_delete_sheet_by_index() {
|
|||||||
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
|
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
|
||||||
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
|
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_sheet_error() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
model.new_sheet();
|
||||||
|
assert!(model.delete_sheet(2).is_err());
|
||||||
|
assert!(model.delete_sheet(1).is_ok());
|
||||||
|
}
|
||||||
|
|||||||
110
base/src/test/test_shift_cells.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::test::util::new_empty_model;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shift_cells_right() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, row, column) = (0, 5, 3); // C5
|
||||||
|
model.set_user_input(sheet, row, column, "Hi".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 1, "world".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 2, "!".to_string());
|
||||||
|
|
||||||
|
model
|
||||||
|
.insert_cells_and_shift_right(sheet, row, column, 1, 1)
|
||||||
|
.unwrap();
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
assert_eq!(model.get_cell_content(0, 5, 3), Ok("".to_string()));
|
||||||
|
assert_eq!(model.get_cell_content(0, 5, 4), Ok("Hi".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shift_cells_right_with_formulas() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, row, column) = (0, 5, 3); // C5
|
||||||
|
model.set_user_input(sheet, row, column - 1, "23".to_string());
|
||||||
|
model.set_user_input(sheet, row, column, "42".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 1, "=C5*2".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 2, "=A5+2".to_string());
|
||||||
|
model.set_user_input(sheet, 20, 3, "=C5*A2".to_string());
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
model
|
||||||
|
.insert_cells_and_shift_right(sheet, row, column, 1, 1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
model.evaluate();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column - 1),
|
||||||
|
Ok("23".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(model.get_cell_content(0, row, column), Ok("".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 1),
|
||||||
|
Ok("42".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 2),
|
||||||
|
Ok("=D5*2".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 3),
|
||||||
|
Ok("=A5+2".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(model.get_cell_content(0, 20, 3), Ok("=D5*A2".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shift_cells_left() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, row, column) = (0, 5, 10); // J5
|
||||||
|
model.set_user_input(sheet, row, column - 1, "23".to_string());
|
||||||
|
model.set_user_input(sheet, row, column, "42".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 1, "Hi".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 2, "honey!".to_string());
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
model
|
||||||
|
.delete_cells_and_shift_left(sheet, row, column, 1, 1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
model.evaluate();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column - 1),
|
||||||
|
Ok("23".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(model.get_cell_content(0, row, column), Ok("Hi".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 1),
|
||||||
|
Ok("honey!".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shift_cells_left_with_formulas() {
|
||||||
|
let mut model = new_empty_model();
|
||||||
|
let (sheet, row, column) = (0, 5, 10); // J5
|
||||||
|
model.set_user_input(sheet, row, column - 1, "23".to_string());
|
||||||
|
model.set_user_input(sheet, row, column, "42".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 1, "33".to_string());
|
||||||
|
model.set_user_input(sheet, row, column + 2, "=K5*A5".to_string());
|
||||||
|
|
||||||
|
model.set_user_input(sheet, row, column + 20, "=K5*A5".to_string());
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
model
|
||||||
|
.delete_cells_and_shift_left(sheet, row, column, 1, 1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
model.evaluate();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 1),
|
||||||
|
Ok("=J5*A5".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_content(0, row, column + 19),
|
||||||
|
Ok("=J5*A5".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
24
base/src/test/test_types.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::types::{Alignment, HorizontalAlignment, VerticalAlignment};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alignment_default() {
|
||||||
|
let alignment = Alignment::default();
|
||||||
|
assert_eq!(
|
||||||
|
alignment,
|
||||||
|
Alignment {
|
||||||
|
horizontal: HorizontalAlignment::General,
|
||||||
|
vertical: VerticalAlignment::Bottom,
|
||||||
|
wrap_text: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let s = serde_json::to_string(&alignment).unwrap();
|
||||||
|
// defaults stringifies as an empty object
|
||||||
|
assert_eq!(s, "{}");
|
||||||
|
|
||||||
|
let a: Alignment = serde_json::from_str("{}").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(a, alignment)
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use crate::{test::util::new_empty_model, types::SheetInfo};
|
use crate::{test::util::new_empty_model, types::SheetProperties};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workbook_worksheets_info() {
|
fn workbook_worksheets_info() {
|
||||||
let model = new_empty_model();
|
let model = new_empty_model();
|
||||||
let sheets_info = model.workbook.get_worksheets_info();
|
let sheets_info = model.get_worksheets_properties();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sheets_info[0],
|
sheets_info[0],
|
||||||
SheetInfo {
|
SheetProperties {
|
||||||
name: "Sheet1".to_string(),
|
name: "Sheet1".to_string(),
|
||||||
state: "visible".to_string(),
|
state: "visible".to_string(),
|
||||||
sheet_id: 1,
|
sheet_id: 1,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
fn test_worksheet_dimension_empty_sheet() {
|
fn test_worksheet_dimension_empty_sheet() {
|
||||||
let model = new_empty_model();
|
let model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 1,
|
min_row: 1,
|
||||||
min_column: 1,
|
min_column: 1,
|
||||||
@@ -25,7 +25,7 @@ fn test_worksheet_dimension_single_cell() {
|
|||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("W11", "1");
|
model._set("W11", "1");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 11,
|
min_row: 11,
|
||||||
min_column: 23,
|
min_column: 23,
|
||||||
@@ -39,9 +39,9 @@ fn test_worksheet_dimension_single_cell() {
|
|||||||
fn test_worksheet_dimension_single_cell_set_empty() {
|
fn test_worksheet_dimension_single_cell_set_empty() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("W11", "1");
|
model._set("W11", "1");
|
||||||
model.set_cell_empty(0, 11, 23).unwrap();
|
model.cell_clear_contents(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 11,
|
min_row: 11,
|
||||||
min_column: 23,
|
min_column: 23,
|
||||||
@@ -55,9 +55,9 @@ fn test_worksheet_dimension_single_cell_set_empty() {
|
|||||||
fn test_worksheet_dimension_single_cell_deleted() {
|
fn test_worksheet_dimension_single_cell_deleted() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("W11", "1");
|
model._set("W11", "1");
|
||||||
model.delete_cell(0, 11, 23).unwrap();
|
model.cell_clear_all(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 1,
|
min_row: 1,
|
||||||
min_column: 1,
|
min_column: 1,
|
||||||
@@ -75,9 +75,9 @@ fn test_worksheet_dimension_multiple_cells() {
|
|||||||
model._set("AA17", "1");
|
model._set("AA17", "1");
|
||||||
model._set("G17", "1");
|
model._set("G17", "1");
|
||||||
model._set("B19", "1");
|
model._set("B19", "1");
|
||||||
model.delete_cell(0, 11, 23).unwrap();
|
model.cell_clear_all(0, 11, 23).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 11,
|
min_row: 11,
|
||||||
min_column: 2,
|
min_column: 2,
|
||||||
@@ -91,7 +91,7 @@ fn test_worksheet_dimension_multiple_cells() {
|
|||||||
fn test_worksheet_dimension_progressive() {
|
fn test_worksheet_dimension_progressive() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 1,
|
min_row: 1,
|
||||||
min_column: 1,
|
min_column: 1,
|
||||||
@@ -102,7 +102,7 @@ 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());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 30,
|
min_row: 30,
|
||||||
min_column: 50,
|
min_column: 50,
|
||||||
@@ -113,7 +113,7 @@ 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());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 10,
|
min_row: 10,
|
||||||
min_column: 15,
|
min_column: 15,
|
||||||
@@ -124,7 +124,7 @@ 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());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 5,
|
min_row: 5,
|
||||||
min_column: 15,
|
min_column: 15,
|
||||||
@@ -135,7 +135,7 @@ 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());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
model.workbook.worksheet(0).unwrap().dimension(),
|
model.workbook.worksheet(0).unwrap().get_dimension(),
|
||||||
WorksheetDimension {
|
WorksheetDimension {
|
||||||
min_row: 5,
|
min_row: 5,
|
||||||
min_column: 15,
|
min_column: 15,
|
||||||
|
|||||||
11
base/src/test/user_model/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
mod test_add_delete_sheets;
|
||||||
|
mod test_clear_cells;
|
||||||
|
mod test_diff_queue;
|
||||||
|
mod test_evaluation;
|
||||||
|
mod test_general;
|
||||||
|
mod test_rename_sheet;
|
||||||
|
mod test_row_column;
|
||||||
|
mod test_shift_cells;
|
||||||
|
mod test_styles;
|
||||||
|
mod test_to_from_bytes;
|
||||||
|
mod test_undo_redo;
|
||||||
84
base/src/test/user_model/test_add_delete_sheets.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::{constants::DEFAULT_COLUMN_WIDTH, UserModel};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_undo_redo() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.new_sheet();
|
||||||
|
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.set_user_input(2, 1, 1, "=Sheet2!B1").unwrap();
|
||||||
|
|
||||||
|
model.undo().unwrap();
|
||||||
|
model.undo().unwrap();
|
||||||
|
|
||||||
|
assert!(model.get_formatted_cell_value(2, 1, 1).is_err());
|
||||||
|
|
||||||
|
model.redo().unwrap();
|
||||||
|
model.redo().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(model.get_formatted_cell_value(2, 1, 1), Ok("6".to_string()));
|
||||||
|
|
||||||
|
model.delete_sheet(1).unwrap();
|
||||||
|
|
||||||
|
assert!(!model.can_undo());
|
||||||
|
assert!(!model.can_redo());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_sheet_color() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.set_sheet_color(0, "#343434").unwrap();
|
||||||
|
let worksheets_properties = model.get_worksheets_properties();
|
||||||
|
assert_eq!(worksheets_properties.len(), 1);
|
||||||
|
assert_eq!(worksheets_properties[0].color, Some("#343434".to_owned()));
|
||||||
|
model.undo().unwrap();
|
||||||
|
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
||||||
|
|
||||||
|
model.redo().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_worksheets_properties()[0].color,
|
||||||
|
Some("#343434".to_owned())
|
||||||
|
);
|
||||||
|
// changes the color if there is one
|
||||||
|
model.set_sheet_color(0, "#2534FF").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_worksheets_properties()[0].color,
|
||||||
|
Some("#2534FF".to_owned())
|
||||||
|
);
|
||||||
|
// Setting it back to none
|
||||||
|
model.set_sheet_color(0, "").unwrap();
|
||||||
|
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_sheet_propagates() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.new_sheet();
|
||||||
|
|
||||||
|
let send_queue = model.flush_send_queue();
|
||||||
|
|
||||||
|
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
let worksheets_properties = model2.get_worksheets_properties();
|
||||||
|
assert_eq!(worksheets_properties.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_sheet_propagates() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.new_sheet();
|
||||||
|
model.delete_sheet(0).unwrap();
|
||||||
|
|
||||||
|
let send_queue = model.flush_send_queue();
|
||||||
|
|
||||||
|
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
let sheets_info = model2.get_worksheets_properties();
|
||||||
|
assert_eq!(sheets_info.len(), 1);
|
||||||
|
}
|
||||||
91
base/src/test/user_model/test_clear_cells.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::{expressions::types::Area, UserModel};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.set_user_input(0, 1, 1, "100$").unwrap();
|
||||||
|
model
|
||||||
|
.range_clear_contents(&Area {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
model.undo().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_formatted_cell_value(0, 1, 1),
|
||||||
|
Ok("100$".to_string())
|
||||||
|
);
|
||||||
|
model.redo().unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
|
||||||
|
model.set_user_input(0, 1, 1, "300").unwrap();
|
||||||
|
// clear contents keeps the formatting
|
||||||
|
assert_eq!(
|
||||||
|
model.get_formatted_cell_value(0, 1, 1),
|
||||||
|
Ok("300$".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
model
|
||||||
|
.range_clear_all(&Area {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
model.undo().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_formatted_cell_value(0, 1, 1),
|
||||||
|
Ok("300$".to_string())
|
||||||
|
);
|
||||||
|
model.redo().unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
model.set_user_input(0, 1, 1, "400").unwrap();
|
||||||
|
// clear contents keeps the formatting
|
||||||
|
assert_eq!(
|
||||||
|
model.get_formatted_cell_value(0, 1, 1),
|
||||||
|
Ok("400".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_empty_cell() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model
|
||||||
|
.range_clear_contents(&Area {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
model.undo().unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_all_empty_cell() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model
|
||||||
|
.range_clear_all(&Area {
|
||||||
|
sheet: 0,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
model.undo().unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
|
||||||
|
}
|
||||||
161
base/src/test/user_model/test_diff_queue.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use crate::{
|
||||||
|
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT},
|
||||||
|
test::util::new_empty_model,
|
||||||
|
UserModel,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_queue() {
|
||||||
|
let mut model1 = UserModel::from_model(new_empty_model());
|
||||||
|
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||||
|
model1.set_column_width(0, 3, width).unwrap();
|
||||||
|
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||||
|
let send_queue = model1.flush_send_queue();
|
||||||
|
|
||||||
|
let mut model2 = UserModel::from_model(new_empty_model());
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 1, 2),
|
||||||
|
Ok("Hello IronCalc!".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply_external_diffs_wrong_str() {
|
||||||
|
let mut model1 = UserModel::from_model(new_empty_model());
|
||||||
|
assert!(model1.apply_external_diffs("invalid".as_bytes()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn queue_undo_redo() {
|
||||||
|
let mut model1 = UserModel::from_model(new_empty_model());
|
||||||
|
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||||
|
model1.set_column_width(0, 3, width).unwrap();
|
||||||
|
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||||
|
assert!(model1.undo().is_ok());
|
||||||
|
assert!(model1.redo().is_ok());
|
||||||
|
let send_queue = model1.flush_send_queue();
|
||||||
|
|
||||||
|
let mut model2 = UserModel::from_model(new_empty_model());
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 1, 2),
|
||||||
|
Ok("Hello IronCalc!".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn queue_undo_redo_multiple() {
|
||||||
|
let mut model1 = UserModel::from_model(new_empty_model());
|
||||||
|
|
||||||
|
// do a bunch of things
|
||||||
|
model1.set_frozen_columns_count(0, 5).unwrap();
|
||||||
|
model1.set_frozen_rows_count(0, 6).unwrap();
|
||||||
|
model1.set_column_width(0, 7, 300.0).unwrap();
|
||||||
|
model1.set_row_height(0, 23, 123.0).unwrap();
|
||||||
|
model1.set_user_input(0, 55, 55, "=42+8").unwrap();
|
||||||
|
|
||||||
|
for row in 1..5 {
|
||||||
|
model1.set_user_input(0, row, 17, "=ROW()").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
model1.insert_row(0, 3).unwrap();
|
||||||
|
model1.insert_row(0, 3).unwrap();
|
||||||
|
|
||||||
|
// undo al of them
|
||||||
|
while model1.can_undo() {
|
||||||
|
model1.undo().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check it is an empty model
|
||||||
|
assert_eq!(model1.get_frozen_columns_count(0), Ok(0));
|
||||||
|
assert_eq!(model1.get_frozen_rows_count(0), Ok(0));
|
||||||
|
assert_eq!(model1.get_column_width(0, 7), Ok(DEFAULT_COLUMN_WIDTH));
|
||||||
|
assert_eq!(
|
||||||
|
model1.get_formatted_cell_value(0, 55, 55),
|
||||||
|
Ok("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(model1.get_row_height(0, 23), Ok(DEFAULT_ROW_HEIGHT));
|
||||||
|
assert_eq!(
|
||||||
|
model1.get_formatted_cell_value(0, 57, 55),
|
||||||
|
Ok("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(model1.get_row_height(0, 25), Ok(DEFAULT_ROW_HEIGHT));
|
||||||
|
|
||||||
|
// redo all of them
|
||||||
|
while model1.can_redo() {
|
||||||
|
model1.redo().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// now send all this to a new model
|
||||||
|
let send_queue = model1.flush_send_queue();
|
||||||
|
let mut model2 = UserModel::from_model(new_empty_model());
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
|
||||||
|
// Check everything is as expected
|
||||||
|
assert_eq!(model2.get_frozen_columns_count(0), Ok(5));
|
||||||
|
assert_eq!(model2.get_frozen_rows_count(0), Ok(6));
|
||||||
|
assert_eq!(model2.get_column_width(0, 7), Ok(300.0));
|
||||||
|
// I inserted two rows
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 57, 55),
|
||||||
|
Ok("50".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(model2.get_row_height(0, 25), Ok(123.0));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 1, 17),
|
||||||
|
Ok("1".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 2, 17),
|
||||||
|
Ok("2".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 3, 17),
|
||||||
|
Ok("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 4, 17),
|
||||||
|
Ok("".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 5, 17),
|
||||||
|
Ok("5".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(0, 6, 17),
|
||||||
|
Ok("6".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_sheet() {
|
||||||
|
let mut model1 = UserModel::from_model(new_empty_model());
|
||||||
|
model1.new_sheet();
|
||||||
|
model1.set_user_input(0, 1, 1, "42").unwrap();
|
||||||
|
model1.set_user_input(1, 1, 1, "=Sheet1!A1*2").unwrap();
|
||||||
|
|
||||||
|
let send_queue = model1.flush_send_queue();
|
||||||
|
let mut model2 = UserModel::from_model(new_empty_model());
|
||||||
|
model2.apply_external_diffs(&send_queue).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model2.get_formatted_cell_value(1, 1, 1),
|
||||||
|
Ok("84".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrong_diffs_handled() {
|
||||||
|
let mut model = UserModel::from_model(new_empty_model());
|
||||||
|
assert!(model
|
||||||
|
.apply_external_diffs("Hello world".as_bytes())
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
31
base/src/test/user_model/test_evaluation.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use crate::UserModel;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn model_evaluates_automatically() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.set_user_input(0, 1, 1, "=1 + 1").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
|
||||||
|
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pause_resume_evaluation() {
|
||||||
|
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||||
|
model.pause_evaluation();
|
||||||
|
model.set_user_input(0, 1, 1, "=1+1").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
model.get_formatted_cell_value(0, 1, 1),
|
||||||
|
Ok("#ERROR!".to_string())
|
||||||
|
);
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
|
||||||
|
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
|
||||||
|
|
||||||
|
model.resume_evaluation();
|
||||||
|
model.set_user_input(0, 2, 1, "=1+4").unwrap();
|
||||||
|
assert_eq!(model.get_formatted_cell_value(0, 2, 1), Ok("5".to_string()));
|
||||||
|
}
|
||||||