├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── example ├── cdn.html ├── datasets.js ├── lib.html └── worker.html ├── js ├── module.js └── worker.js ├── package.json ├── src ├── formulas.rs ├── lib.rs ├── style.rs ├── utils.rs └── xml.rs ├── vite.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /dist 3 | /target 4 | /node_modules 5 | **/*.rs.bk 6 | *.log -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.1.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "bumpalo" 22 | version = "3.17.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 25 | 26 | [[package]] 27 | name = "byteorder" 28 | version = "1.5.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 31 | 32 | [[package]] 33 | name = "cc" 34 | version = "1.2.12" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" 37 | dependencies = [ 38 | "shlex", 39 | ] 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "0.1.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 52 | 53 | [[package]] 54 | name = "console_error_panic_hook" 55 | version = "0.1.7" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 58 | dependencies = [ 59 | "cfg-if 1.0.0", 60 | "wasm-bindgen", 61 | ] 62 | 63 | [[package]] 64 | name = "crc32fast" 65 | version = "1.4.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 68 | dependencies = [ 69 | "cfg-if 1.0.0", 70 | ] 71 | 72 | [[package]] 73 | name = "crossbeam-utils" 74 | version = "0.8.21" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 77 | 78 | [[package]] 79 | name = "flate2" 80 | version = "1.0.35" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 83 | dependencies = [ 84 | "crc32fast", 85 | "miniz_oxide", 86 | ] 87 | 88 | [[package]] 89 | name = "gloo-utils" 90 | version = "0.1.7" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" 93 | dependencies = [ 94 | "js-sys", 95 | "serde", 96 | "serde_json", 97 | "wasm-bindgen", 98 | "web-sys", 99 | ] 100 | 101 | [[package]] 102 | name = "itoa" 103 | version = "1.0.14" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 106 | 107 | [[package]] 108 | name = "js-sys" 109 | version = "0.3.77" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 112 | dependencies = [ 113 | "once_cell", 114 | "wasm-bindgen", 115 | ] 116 | 117 | [[package]] 118 | name = "json2excel-wasm" 119 | version = "0.2.0" 120 | dependencies = [ 121 | "console_error_panic_hook", 122 | "gloo-utils", 123 | "regex", 124 | "serde", 125 | "serde-wasm-bindgen", 126 | "wasm-bindgen", 127 | "wasm-bindgen-test", 128 | "web-sys", 129 | "wee_alloc", 130 | "zip", 131 | ] 132 | 133 | [[package]] 134 | name = "libc" 135 | version = "0.2.169" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 138 | 139 | [[package]] 140 | name = "log" 141 | version = "0.4.25" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 144 | 145 | [[package]] 146 | name = "memchr" 147 | version = "2.7.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 150 | 151 | [[package]] 152 | name = "memory_units" 153 | version = "0.4.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 156 | 157 | [[package]] 158 | name = "minicov" 159 | version = "0.3.7" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" 162 | dependencies = [ 163 | "cc", 164 | "walkdir", 165 | ] 166 | 167 | [[package]] 168 | name = "miniz_oxide" 169 | version = "0.8.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" 172 | dependencies = [ 173 | "adler2", 174 | ] 175 | 176 | [[package]] 177 | name = "once_cell" 178 | version = "1.20.2" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 181 | 182 | [[package]] 183 | name = "proc-macro2" 184 | version = "1.0.93" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 187 | dependencies = [ 188 | "unicode-ident", 189 | ] 190 | 191 | [[package]] 192 | name = "quote" 193 | version = "1.0.38" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 196 | dependencies = [ 197 | "proc-macro2", 198 | ] 199 | 200 | [[package]] 201 | name = "regex" 202 | version = "1.11.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 205 | dependencies = [ 206 | "aho-corasick", 207 | "memchr", 208 | "regex-automata", 209 | "regex-syntax", 210 | ] 211 | 212 | [[package]] 213 | name = "regex-automata" 214 | version = "0.4.9" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 217 | dependencies = [ 218 | "aho-corasick", 219 | "memchr", 220 | "regex-syntax", 221 | ] 222 | 223 | [[package]] 224 | name = "regex-syntax" 225 | version = "0.8.5" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 228 | 229 | [[package]] 230 | name = "rustversion" 231 | version = "1.0.19" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 234 | 235 | [[package]] 236 | name = "ryu" 237 | version = "1.0.19" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 240 | 241 | [[package]] 242 | name = "same-file" 243 | version = "1.0.6" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 246 | dependencies = [ 247 | "winapi-util", 248 | ] 249 | 250 | [[package]] 251 | name = "serde" 252 | version = "1.0.217" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 255 | dependencies = [ 256 | "serde_derive", 257 | ] 258 | 259 | [[package]] 260 | name = "serde-wasm-bindgen" 261 | version = "0.5.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" 264 | dependencies = [ 265 | "js-sys", 266 | "serde", 267 | "wasm-bindgen", 268 | ] 269 | 270 | [[package]] 271 | name = "serde_derive" 272 | version = "1.0.217" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 275 | dependencies = [ 276 | "proc-macro2", 277 | "quote", 278 | "syn", 279 | ] 280 | 281 | [[package]] 282 | name = "serde_json" 283 | version = "1.0.138" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 286 | dependencies = [ 287 | "itoa", 288 | "memchr", 289 | "ryu", 290 | "serde", 291 | ] 292 | 293 | [[package]] 294 | name = "shlex" 295 | version = "1.3.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 298 | 299 | [[package]] 300 | name = "syn" 301 | version = "2.0.98" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 304 | dependencies = [ 305 | "proc-macro2", 306 | "quote", 307 | "unicode-ident", 308 | ] 309 | 310 | [[package]] 311 | name = "unicode-ident" 312 | version = "1.0.16" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 315 | 316 | [[package]] 317 | name = "walkdir" 318 | version = "2.5.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 321 | dependencies = [ 322 | "same-file", 323 | "winapi-util", 324 | ] 325 | 326 | [[package]] 327 | name = "wasm-bindgen" 328 | version = "0.2.100" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 331 | dependencies = [ 332 | "cfg-if 1.0.0", 333 | "once_cell", 334 | "rustversion", 335 | "wasm-bindgen-macro", 336 | ] 337 | 338 | [[package]] 339 | name = "wasm-bindgen-backend" 340 | version = "0.2.100" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 343 | dependencies = [ 344 | "bumpalo", 345 | "log", 346 | "proc-macro2", 347 | "quote", 348 | "syn", 349 | "wasm-bindgen-shared", 350 | ] 351 | 352 | [[package]] 353 | name = "wasm-bindgen-futures" 354 | version = "0.4.50" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 357 | dependencies = [ 358 | "cfg-if 1.0.0", 359 | "js-sys", 360 | "once_cell", 361 | "wasm-bindgen", 362 | "web-sys", 363 | ] 364 | 365 | [[package]] 366 | name = "wasm-bindgen-macro" 367 | version = "0.2.100" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 370 | dependencies = [ 371 | "quote", 372 | "wasm-bindgen-macro-support", 373 | ] 374 | 375 | [[package]] 376 | name = "wasm-bindgen-macro-support" 377 | version = "0.2.100" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 380 | dependencies = [ 381 | "proc-macro2", 382 | "quote", 383 | "syn", 384 | "wasm-bindgen-backend", 385 | "wasm-bindgen-shared", 386 | ] 387 | 388 | [[package]] 389 | name = "wasm-bindgen-shared" 390 | version = "0.2.100" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 393 | dependencies = [ 394 | "unicode-ident", 395 | ] 396 | 397 | [[package]] 398 | name = "wasm-bindgen-test" 399 | version = "0.3.50" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" 402 | dependencies = [ 403 | "js-sys", 404 | "minicov", 405 | "wasm-bindgen", 406 | "wasm-bindgen-futures", 407 | "wasm-bindgen-test-macro", 408 | ] 409 | 410 | [[package]] 411 | name = "wasm-bindgen-test-macro" 412 | version = "0.3.50" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" 415 | dependencies = [ 416 | "proc-macro2", 417 | "quote", 418 | "syn", 419 | ] 420 | 421 | [[package]] 422 | name = "web-sys" 423 | version = "0.3.77" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 426 | dependencies = [ 427 | "js-sys", 428 | "wasm-bindgen", 429 | ] 430 | 431 | [[package]] 432 | name = "wee_alloc" 433 | version = "0.4.5" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 436 | dependencies = [ 437 | "cfg-if 0.1.10", 438 | "libc", 439 | "memory_units", 440 | "winapi", 441 | ] 442 | 443 | [[package]] 444 | name = "winapi" 445 | version = "0.3.9" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 448 | dependencies = [ 449 | "winapi-i686-pc-windows-gnu", 450 | "winapi-x86_64-pc-windows-gnu", 451 | ] 452 | 453 | [[package]] 454 | name = "winapi-i686-pc-windows-gnu" 455 | version = "0.4.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 458 | 459 | [[package]] 460 | name = "winapi-util" 461 | version = "0.1.9" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 464 | dependencies = [ 465 | "windows-sys", 466 | ] 467 | 468 | [[package]] 469 | name = "winapi-x86_64-pc-windows-gnu" 470 | version = "0.4.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 473 | 474 | [[package]] 475 | name = "windows-sys" 476 | version = "0.59.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 479 | dependencies = [ 480 | "windows-targets", 481 | ] 482 | 483 | [[package]] 484 | name = "windows-targets" 485 | version = "0.52.6" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 488 | dependencies = [ 489 | "windows_aarch64_gnullvm", 490 | "windows_aarch64_msvc", 491 | "windows_i686_gnu", 492 | "windows_i686_gnullvm", 493 | "windows_i686_msvc", 494 | "windows_x86_64_gnu", 495 | "windows_x86_64_gnullvm", 496 | "windows_x86_64_msvc", 497 | ] 498 | 499 | [[package]] 500 | name = "windows_aarch64_gnullvm" 501 | version = "0.52.6" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 504 | 505 | [[package]] 506 | name = "windows_aarch64_msvc" 507 | version = "0.52.6" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 510 | 511 | [[package]] 512 | name = "windows_i686_gnu" 513 | version = "0.52.6" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 516 | 517 | [[package]] 518 | name = "windows_i686_gnullvm" 519 | version = "0.52.6" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 522 | 523 | [[package]] 524 | name = "windows_i686_msvc" 525 | version = "0.52.6" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 528 | 529 | [[package]] 530 | name = "windows_x86_64_gnu" 531 | version = "0.52.6" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 534 | 535 | [[package]] 536 | name = "windows_x86_64_gnullvm" 537 | version = "0.52.6" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 540 | 541 | [[package]] 542 | name = "windows_x86_64_msvc" 543 | version = "0.52.6" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 546 | 547 | [[package]] 548 | name = "zip" 549 | version = "0.6.6" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 552 | dependencies = [ 553 | "byteorder", 554 | "crc32fast", 555 | "crossbeam-utils", 556 | "flate2", 557 | ] 558 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json2excel-wasm" 3 | version = "0.2.0" 4 | authors = ["Aleksei Kolosov ", "Maksim Kozhukh "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/lib.rs" 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [features] 12 | default = ["console_error_panic_hook"] 13 | 14 | [profile.release] 15 | lto = true 16 | opt-level = "s" 17 | debug = false 18 | panic = "abort" 19 | 20 | [dependencies] 21 | wasm-bindgen = "0.2.84" 22 | serde = { version="^1.0.160", features = ["derive"] } 23 | serde-wasm-bindgen = "0.5.0" 24 | zip = { version = "0.6.4", default-features = false, features = ["deflate"] } 25 | wee_alloc = { version = "0.4.5", optional = true } 26 | console_error_panic_hook = { version = "0.1.6", optional = true } 27 | gloo-utils = { version = "0.1", features = ["serde"] } 28 | regex = { version = "1.7.3", default-features = false, features = ["std" , "perf"] } 29 | 30 | [dependencies.web-sys] 31 | version = "0.3" 32 | features = [ 33 | "console", 34 | ] 35 | 36 | [dev-dependencies] 37 | wasm-bindgen-test = "0.3.13" 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON2Excel 2 | -------------- 3 | 4 | JSON2Excel is a Rust and WebAssembly-based library that allows converting JSON files into Excel ones at ease. 5 | 6 | [![npm version](https://badge.fury.io/js/json2excel-wasm.svg)](https://badge.fury.io/js/json2excel-wasm) 7 | 8 | ### How to build 9 | 10 | ``` 11 | 12 | cargo install wasm-pack 13 | wasm-pack build 14 | ``` 15 | 16 | ### How to use via npm 17 | 18 | - install the module 19 | 20 | ```js 21 | yarn add json2excel-wasm 22 | ``` 23 | - import and use the module 24 | 25 | ```js 26 | // worker.js 27 | import { convert } "json2excel-wasm"; 28 | const blob = await convert(json_data_to_export); 29 | ``` 30 | 31 | you can use code like next to force download of the result blob 32 | 33 | ```js 34 | const a = document.createElement("a"); 35 | a.href = URL.createObjectURL(blob); 36 | a.download = "data.xlsx"; 37 | document.body.append(a); 38 | a.click(); 39 | document.body.remove(a); 40 | ``` 41 | 42 | ### How to use from CDN 43 | 44 | CDN links are the following: 45 | 46 | - https://cdn.dhtmlx.com/libs/json2excel/1.3/worker.js 47 | - https://cdn.dhtmlx.com/libs/json2excel/1.3/module.js 48 | 49 | You can import and use lib dynamically like 50 | 51 | ```js 52 | const convert = import("https://cdn.dhtmlx.com/libs/json2excel/1.3/module.js"); 53 | const blob = await convert(json_data_to_export); 54 | ``` 55 | 56 | or use it as web worker 57 | 58 | ```js 59 | // you need to server worker from the same domain as the main script 60 | var worker = new Worker("./worker.js"); 61 | worker.addEventListener("message", ev => { 62 | if (ev.data.type === "ready"){ 63 | const blob = ev.data.blob; 64 | // do something with result 65 | } 66 | }); 67 | worker.postMessage({ 68 | type:"convert", 69 | data: raw_json_data 70 | }); 71 | ``` 72 | 73 | if you want to load worker script from CDN and not from your domain it requires a more complicated approach, as you need to catch the moment when service inside of the worker will be fully initialized 74 | 75 | ```js 76 | var url = window.URL.createObjectURL(new Blob([ 77 | "importScripts('https://cdn.dhtmlx.com/libs/json2excel/1.3/worker.js');" 78 | ], { type: "text/javascript" })); 79 | 80 | var worker = new Promise((res) => { 81 | const x = Worker(url); 82 | worker.addEventListener("message", ev => { 83 | if (ev.data.type === "ready"){ 84 | const json = ev.data.data; 85 | // do something with result 86 | } else if (ev.data.type === "init"){ 87 | // service is ready 88 | res(x); 89 | } 90 | }); 91 | }); 92 | 93 | worker.then(x => x.postMessage({ 94 | type:"convert", 95 | data: raw_json_data 96 | })); 97 | ``` 98 | 99 | 100 | ### Input format 101 | 102 | ```ts 103 | interface IConvertMessageData { 104 | uid?: string; 105 | data: ISheetData; 106 | styles?: IStyles[]; 107 | wasmPath?: string; // use cdn by default 108 | } 109 | 110 | interface IReadyMessageData { 111 | uid: string; // same as incoming uid 112 | blob: Blob; 113 | } 114 | 115 | interface ISheetData { 116 | name?: string; 117 | cols?: IColumnData[]; 118 | rows?: IRowData[]; 119 | cells?: IDataCell[][]; // if cells mising, use plain 120 | plain?: string[][]; 121 | 122 | merged?: IMergedCells; 123 | } 124 | 125 | interface IMergedCells { 126 | from: IDataPoint; 127 | to: IDataPoint; 128 | } 129 | 130 | interface IDataPoint { 131 | column: number; 132 | row: number; 133 | } 134 | 135 | interface IColumnData { 136 | width: number; 137 | } 138 | 139 | interface IRowData { 140 | height: number; 141 | } 142 | 143 | interface IDataCell{ 144 | v: string; 145 | s: number; 146 | } 147 | 148 | interface IStyle { 149 | fontSize?: string; 150 | align?: string; // left | center | right 151 | verticalAlign?: string; // top | center | bottom 152 | 153 | background?: string; 154 | color?: string; 155 | 156 | fontWeight?: string; // bold 157 | fontStyle?: string; // italic 158 | textDecoration?: string; // underline 159 | 160 | format?: string; 161 | 162 | // border valie format: {size} {style} {color} 163 | // size: 0.5px | 1px | 2px (works only with 'solid' style) 164 | // style: dashed | dotted | double | thin | solid 165 | // color: #000 | #000000 166 | borderTop?: string; 167 | borderRight?: string; 168 | borderBottom?: string; 169 | borderLeft?: string; 170 | } 171 | ``` 172 | 173 | 174 | ### License 175 | 176 | MIT 177 | -------------------------------------------------------------------------------- /example/cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - api 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/datasets.js: -------------------------------------------------------------------------------- 1 | var example = { 2 | "data": [ 3 | { 4 | "name": "Sheet1", 5 | "cells": [ 6 | [ 7 | { 8 | "v": "Report - July 2016", 9 | "s": 14 10 | }, 11 | { 12 | "v": null, 13 | "s": 15 14 | }, 15 | { 16 | "v": null, 17 | "s": 15 18 | }, 19 | { 20 | "v": null, 21 | "s": 15 22 | }, 23 | { 24 | "v": null, 25 | "s": 15 26 | } 27 | ], 28 | [ 29 | { 30 | "v": "Region", 31 | "s": 2 32 | }, 33 | { 34 | "v": "Country", 35 | "s": 2 36 | }, 37 | { 38 | "v": "Sales - Group A", 39 | "s": 3 40 | }, 41 | { 42 | "v": "Sales - Group B", 43 | "s": 3 44 | }, 45 | { 46 | "v": "Total", 47 | "s": 4 48 | } 49 | ], 50 | [ 51 | { 52 | "v": "Europe", 53 | "s": 5 54 | }, 55 | { 56 | "v": "Germany", 57 | "s": 6 58 | }, 59 | { 60 | "v": "188400", 61 | "s": 7 62 | }, 63 | { 64 | "v": "52000", 65 | "s": 7 66 | }, 67 | { 68 | "v": "240400", 69 | "s": 7 70 | } 71 | ], 72 | [ 73 | { 74 | "v": "Europe", 75 | "s": 5 76 | }, 77 | { 78 | "v": "France", 79 | "s": 5 80 | }, 81 | { 82 | "v": "192200", 83 | "s": 8 84 | }, 85 | { 86 | "v": "12000", 87 | "s": 9 88 | }, 89 | { 90 | "v": "204200", 91 | "s": 7 92 | } 93 | ], 94 | [ 95 | { 96 | "v": "Europe", 97 | "s": 5 98 | }, 99 | { 100 | "v": "Poland", 101 | "s": 6 102 | }, 103 | { 104 | "v": "68900", 105 | "s": 7 106 | }, 107 | { 108 | "v": "8000", 109 | "s": 7 110 | }, 111 | { 112 | "v": "76900", 113 | "s": 7 114 | } 115 | ], 116 | [ 117 | { 118 | "v": "Asia", 119 | "s": 5 120 | }, 121 | { 122 | "v": "Japan", 123 | "s": 5 124 | }, 125 | { 126 | "v": "140000", 127 | "s": 10 128 | }, 129 | { 130 | "v": "14000", 131 | "s": 11 132 | }, 133 | { 134 | "v": "154000", 135 | "s": 7 136 | } 137 | ], 138 | [ 139 | { 140 | "v": "Asia", 141 | "s": 5 142 | }, 143 | { 144 | "v": "China", 145 | "s": 5 146 | }, 147 | { 148 | "v": "50000", 149 | "s": 7 150 | }, 151 | { 152 | "v": "4800", 153 | "s": 7 154 | }, 155 | { 156 | "v": "54800", 157 | "s": 7 158 | } 159 | ] 160 | ], 161 | "merged": [ 162 | { 163 | "from": { 164 | "column": 0, 165 | "row": 0 166 | }, 167 | "to": { 168 | "column": 4, 169 | "row": 0 170 | } 171 | } 172 | ] 173 | } 174 | ], 175 | "styles": [ 176 | { 177 | "fontFamily": "Calibri", 178 | "fontSize": "16px", 179 | "color": "rgba(0,0,0,1)", 180 | "borderRight": "1px solid #34e343", 181 | "borderLeft": "1px solid #34e343", 182 | "format": "General", 183 | "borderTop": "1px solid #34e343", 184 | "borderBottom": "1px solid #34e343" 185 | }, 186 | { 187 | "fontSize": "16px", 188 | "fontFamily": "Calibri", 189 | "borderLeft": "0.5px solid #000", 190 | "borderTop": "0.5px solid #000", 191 | "borderRight": "0.5px solid #000", 192 | "format": "General", 193 | "borderBottom": "0.5px solid #000" 194 | }, 195 | { 196 | "fontWeight": "bold", 197 | "borderBottom": "1px dashed #701243", 198 | "verticalAlign": "top", 199 | "background": "rgba(234,234,234,1)", 200 | "fontSize": "15px", 201 | "fontFamily": "PT Sans", 202 | "borderTop": "1px dashed #701243", 203 | "format": "General", 204 | "color": "rgba(129,129,129,1)", 205 | "borderLeft": "1px dashed #701243", 206 | "borderRight": "1px dashed #701243", 207 | "align": "center" 208 | }, 209 | { 210 | "color": "rgba(129,129,129,1)", 211 | "borderBottom": "1px dotted #000", 212 | "fontSize": "15px", 213 | "fontFamily": "PT Sans", 214 | "verticalAlign": "center", 215 | "format": "General", 216 | "align": "center", 217 | "borderTop": "1px dotted #000", 218 | "fontWeight": "bold", 219 | "borderRight": "1px dotted #000", 220 | "borderLeft": "1px dotted #000" 221 | }, 222 | { 223 | "fontFamily": "PT Sans", 224 | "borderBottom": "1px double #000", 225 | "borderTop": "1px double #000", 226 | "fontSize": "15px", 227 | "verticalAlign": "center", 228 | "color": "rgba(129,129,129,1)", 229 | "fontWeight": "bold", 230 | "align": "right", 231 | "format": "General", 232 | "borderLeft": "1px double #000", 233 | "borderRight": "1px double #000" 234 | }, 235 | { 236 | "format": "General", 237 | "borderBottom": "1px solid #94ff4d", 238 | "background": "rgba(234,234,234,1)", 239 | "verticalAlign": "top", 240 | "align": "center", 241 | "borderTop": "1px solid #94ff4d", 242 | "borderLeft": "1px solid #94ff4d", 243 | "borderRight": "1px solid #94ff4d", 244 | "color": "rgba(129,129,129,1)", 245 | "fontSize": "15px", 246 | "fontFamily": "PT Sans" 247 | }, 248 | { 249 | "align": "center", 250 | "borderLeft": "1px solid #000", 251 | "fontWeight": "bold", 252 | "background": "rgba(234,234,234,1)", 253 | "fontStyle": "italic", 254 | "borderRight": "1px solid #000", 255 | "textDecoration": "underline", 256 | "color": "rgba(129,129,129,1)", 257 | "format": "General", 258 | "fontFamily": "PT Sans", 259 | "fontSize": "15px", 260 | "verticalAlign": "bottom", 261 | "borderBottom": "1px solid #000", 262 | "borderTop": "1px solid #000" 263 | }, 264 | { 265 | "fontFamily": "PT Sans", 266 | "fontSize": "15px", 267 | "borderRight": "2px solid #000", 268 | "borderBottom": "2px solid #000", 269 | "format": "General", 270 | "color": "rgba(0,0,0,1)", 271 | "verticalAlign": "top", 272 | "borderLeft": "2px solid #000", 273 | "borderTop": "2px solid #000", 274 | "align": "right" 275 | }, 276 | { 277 | "fontFamily": "PT Sans", 278 | "borderTop": "1px solid #000", 279 | "verticalAlign": "top", 280 | "background": "rgba(152,0,0,1)", 281 | "color": "rgba(0,0,0,1)", 282 | "align": "right", 283 | "borderBottom": "1px solid #000", 284 | "format": "General", 285 | "borderLeft": "1px solid #000", 286 | "fontSize": "15px", 287 | "borderRight": "1px solid #000" 288 | }, 289 | { 290 | "fontSize": "32px", 291 | "borderLeft": "1px solid #000", 292 | "borderBottom": "1px solid #000", 293 | "verticalAlign": "top", 294 | "format": "General", 295 | "color": "rgba(0,0,0,1)", 296 | "fontFamily": "PT Sans", 297 | "borderTop": "1px solid #000", 298 | "borderRight": "1px solid #000", 299 | "align": "right" 300 | }, 301 | { 302 | "borderTop": "1px solid #000", 303 | "align": "right", 304 | "format": "General", 305 | "fontFamily": "PT Sans", 306 | "borderBottom": "1px solid #000", 307 | "fontSize": "15px", 308 | "borderRight": "1px solid #000", 309 | "borderLeft": "1px solid #000", 310 | "verticalAlign": "top", 311 | "color": "rgba(60,120,216,1)" 312 | }, 313 | { 314 | "borderLeft": "1px solid #000", 315 | "format": "General", 316 | "borderTop": "1px solid #000", 317 | "color": "rgba(0,0,0,1)", 318 | "fontSize": "15px", 319 | "align": "right", 320 | "borderBottom": "1px solid #000", 321 | "borderRight": "1px solid #000", 322 | "fontFamily": "Impact", 323 | "verticalAlign": "top" 324 | }, 325 | { 326 | "background": "rgba(255,255,0,1)", 327 | "borderLeft": "1px solid #000000", 328 | "borderTop": "1px solid #000000", 329 | "borderRight": "1px solid #000000", 330 | "fontSize": "16px", 331 | "borderBottom": "1px solid #000000", 332 | "color": "rgba(255,0,0,1)", 333 | "format": "General", 334 | "fontFamily": "Calibri" 335 | }, 336 | { 337 | "borderBottom": "1px solid #000", 338 | "borderTop": "1px solid #000", 339 | "color": "rgba(0,0,0,1)", 340 | "fontSize": "16px", 341 | "fontFamily": "Calibri", 342 | "borderRight": "1px solid #000", 343 | "borderLeft": "1px solid #000", 344 | "format": "General" 345 | }, 346 | { 347 | "color": "rgba(255,239,239,1)", 348 | "borderRight": "1px solid #000", 349 | "background": "rgba(110,110,255,1)", 350 | "align": "center", 351 | "borderLeft": "1px solid #000", 352 | "borderTop": "1px solid #000", 353 | "fontSize": "16px", 354 | "borderBottom": "1px solid #000", 355 | "format": "General", 356 | "fontFamily": "PT Sans" 357 | }, 358 | { 359 | "borderBottom": "1px solid #000", 360 | "format": "General", 361 | "borderRight": "1px solid #000", 362 | "fontFamily": "Calibri", 363 | "fontSize": "16px", 364 | "borderTop": "1px solid #000", 365 | "borderLeft": "1px solid #000" 366 | } 367 | ] 368 | } -------------------------------------------------------------------------------- /example/lib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - api 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/worker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - worker 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /js/module.js: -------------------------------------------------------------------------------- 1 | import init, { import_to_xlsx } from "../pkg/json2excel_wasm.js"; 2 | 3 | export { import_to_xlsx as toExcel }; 4 | export async function convert(data){ 5 | await init(); 6 | 7 | if (typeof data === "string") 8 | data = JSON.parse(data); 9 | 10 | const result = import_to_xlsx(data); 11 | const blob = new Blob([result], { 12 | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," 13 | }); 14 | 15 | return blob; 16 | } 17 | -------------------------------------------------------------------------------- /js/worker.js: -------------------------------------------------------------------------------- 1 | import init, { import_to_xlsx } from '../pkg/json2excel_wasm.js'; 2 | 3 | 4 | onmessage = function(e) { 5 | if (e.data.type === "convert") { 6 | let data = e.data.data; 7 | if (typeof data === "string") 8 | data = JSON.parse(data); 9 | doConvert(data); 10 | } 11 | } 12 | 13 | async function doConvert(data, config = {}){ 14 | await init(); 15 | 16 | const result = import_to_xlsx(data); 17 | const blob = new Blob([result], { 18 | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," 19 | }); 20 | 21 | postMessage({ 22 | uid: config.uid || (new Date()).valueOf(), 23 | type: "ready", 24 | blob 25 | }); 26 | } 27 | 28 | postMessage({ type:"init" }); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json2excel-wasm", 3 | "description": "WASM based web worker for converting excel data to raw json", 4 | "keywords": ["excel", "convert", "wasm"], 5 | "version": "1.3.1", 6 | "license": "MIT", 7 | "collaborators": [ 8 | "Aleksei Kolosov ", 9 | "Maksim Kozhukh " 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/DHTMLX/json2excel" 14 | }, 15 | "files": [ 16 | "dist/worker.js", 17 | "dist/module.js" 18 | ], 19 | "module": "dist/module.js", 20 | "scripts": { 21 | "build:wasm": "cargo build && wasm-pack build --target web", 22 | "build:module": "vite build --mode module", 23 | "build:worker": "vite build --mode worker", 24 | "build": "yarn build:wasm && yarn build:module && yarn build:worker" 25 | }, 26 | "devDependencies": { 27 | "vite": "^4.2.0", 28 | "vite-plugin-wasm": "^3.2.2", 29 | "vite-plugin-top-level-await": "^1.3.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/formulas.rs: -------------------------------------------------------------------------------- 1 | use regex::{ Regex, Captures }; 2 | use std::collections::HashSet; 3 | 4 | pub fn fix_formula(line: &str, futures: &HashSet<&str>) -> String { 5 | let re = Regex::new(r"([A-Z\.]+)\(").unwrap(); 6 | let fixed = str::replace(line, ";", ",").clone(); 7 | 8 | 9 | let out = re.replace(fixed.as_str(), |caps: &Captures| { 10 | let op = caps.get(1).unwrap().as_str(); 11 | if futures.contains(op) { 12 | return format!("_xlfn.{}(", op); 13 | } 14 | return caps.get(0).unwrap().as_str().to_string(); 15 | }); 16 | 17 | out.into_owned() 18 | } 19 | 20 | pub fn get_future_functions() -> HashSet<&'static str>{ 21 | let future: HashSet<&str> = vec!["ACOT", "ACOTH", "AGGREGATE", "ARABIC", "ARRAYTOTEXT", "BASE", "BETA.DIST", "BETA.INV", "BINOM.DIST", "BINOM.DIST.RANGE", "BINOM.INV", "BITAND", "BITLSHIFT", "BITOR", "BITRSHIFT", "BITXOR", "CEILING.MATH", "CEILING.PRECISE", "CHISQ.DIST", "CHISQ.DIST.RT", "CHISQ.INV", "CHISQ.INV.RT", "CHISQ.TEST", "COMBINA", "CONCAT", "CONFIDENCE.NORM", "CONFIDENCE.T", "COT", "COTH", "COVARIANCE.P", "COVARIANCE.S", "CSC", "CSCH", "DAYS", "DECIMAL", "ECMA.CEILING", "ERF.PRECISE", "ERFC.PRECISE", "EXPON.DIST", "F.DIST", "F.DIST.RT", "F.INV", "F.INV.RT", "F.TEST", "FIELDVALUE", "FILTERXML", "FLOOR.MATH", "FLOOR.PRECISE", "FORECAST.ETS", "FORECAST.ETS.CONFINT", "FORECAST.ETS.SEASONALITY", "FORECAST.ETS.STAT", "FORECAST.LINEAR", "FORMULATEXT", "GAMMA", "GAMMA.DIST", "GAMMA.INV", "GAMMALN.PRECISE", "GAUSS", "HYPGEOM.DIST", "IFNA", "IFS", "IMCOSH", "IMCOT", "IMCSC", "IMCSCH", "IMSEC", "IMSECH", "IMSINH", "IMTAN", "ISFORMULA", "ISO.CEILING", "ISOWEEKNUM", "LET", "LOGNORM.DIST", "LOGNORM.INV", "MAXIFS", "MINIFS", "MODE.MULT", "MODE.SNGL", "MUNIT", "NEGBINOM.DIST", "NETWORKDAYS.INTL", "NORM.DIST", "NORM.INV", "NORM.S.DIST", "NORM.S.INV", "NUMBERVALUE", "PDURATION", "PERCENTILE.EXC", "PERCENTILE.INC", "PERCENTRANK.EXC", "PERCENTRANK.INC", "PERMUTATIONA", "PHI", "POISSON.DIST", "QUARTILE.EXC", "QUARTILE.INC", "QUERYSTRING", "RANDARRAY", "RANK.AVG", "RANK.EQ", "RRI", "SEC", "SECH", "SEQUENCE", "SHEET", "SHEETS", "SKEW.P", "SORTBY", "STDEV.P", "STDEV.S", "SWITCH", "T.DIST", "T.DIST.2T", "T.DIST.RT", "T.INV", "T.INV.2T", "T.TEST", "TEXTJOIN", "UNICHAR", "UNICODE", "UNIQUE", "VAR.P", "VAR.S", "WEBSERVICE", "WEIBULL.DIST", "WORKDAY.INTL", "XLOOKUP", "XMATCH", "XOR", "Z.TEST"].iter().cloned().collect(); 22 | future 23 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen; 2 | use wasm_bindgen::prelude::*; 3 | use std::io::prelude::*; 4 | 5 | // serializing helpers 6 | use serde::Deserialize; 7 | use gloo_utils::format::JsValueSerdeExt; 8 | 9 | use zip; 10 | 11 | use zip::write::FileOptions; 12 | 13 | use std::io::Cursor; 14 | 15 | use std::collections::HashMap; 16 | 17 | type Dict = HashMap; 18 | 19 | pub mod xml; 20 | use crate::xml::Element; 21 | pub mod style; 22 | use crate::style::StyleTable; 23 | pub mod utils; 24 | pub mod formulas; 25 | 26 | const WIDTH_COEF: f32 = 8.5; 27 | const HEIGHT_COEF: f32 = 0.75; 28 | 29 | const ROOT_RELS: &'static [u8] = br#" 30 | "#; 31 | 32 | 33 | #[derive(Deserialize)] 34 | pub struct ColumnData { 35 | pub width: f32, 36 | } 37 | 38 | #[derive(Deserialize)] 39 | pub struct RowData { 40 | pub height: f32, 41 | } 42 | 43 | #[derive(Deserialize)] 44 | pub struct CellCoords { 45 | pub column: u32, 46 | pub row: u32, 47 | } 48 | 49 | #[derive(Deserialize)] 50 | pub struct MergedCell { 51 | pub from: CellCoords, 52 | pub to: CellCoords, 53 | } 54 | 55 | #[derive(Deserialize)] 56 | pub struct Cell { 57 | pub v: Option, 58 | pub s: Option, 59 | } 60 | 61 | #[derive(Deserialize)] 62 | pub struct SheetData { 63 | name: Option, 64 | cells: Option>>>, 65 | plain: Option>>>, 66 | cols: Option>>, 67 | rows: Option>>, 68 | merged: Option>, 69 | } 70 | 71 | #[derive(Deserialize)] 72 | pub struct SpreadsheetData { 73 | data: Vec, 74 | styles: Option>, 75 | } 76 | 77 | struct InnerCell { 78 | cell: String, 79 | value: CellValue, 80 | style: Option 81 | } 82 | 83 | impl InnerCell { 84 | pub fn new(cell: String, style: &Option) -> InnerCell { 85 | InnerCell { 86 | cell, 87 | value: CellValue::None, 88 | style: style.to_owned() 89 | } 90 | } 91 | } 92 | 93 | enum CellValue { 94 | None, 95 | Value(String), 96 | Formula(String), 97 | SharedString(u32) 98 | } 99 | 100 | #[wasm_bindgen] 101 | pub fn import_to_xlsx(raw_data: &JsValue) -> Vec { 102 | utils::set_panic_hook(); 103 | 104 | let data: SpreadsheetData = raw_data.into_serde().unwrap(); 105 | let futures = formulas::get_future_functions(); 106 | 107 | let mut shared_strings = vec!(); 108 | let mut shared_strings_count = 0; 109 | let style_table = StyleTable::new(data.styles); 110 | 111 | let mut sheets_info: Vec<(String, String)> = vec!(); 112 | 113 | let buf: Vec = vec!(); 114 | let w = Cursor::new(buf); 115 | let mut zip = zip::ZipWriter::new(w); 116 | let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored).unix_permissions(0o755); 117 | 118 | for (sheet_index, sheet) in data.data.iter().enumerate() { 119 | let mut rows: Vec> = vec!(); 120 | match &sheet.cells { 121 | Some(cells) => { 122 | for (row_index, row) in cells.iter().enumerate() { 123 | let mut inner_row: Vec = vec!(); 124 | for (col_index, cell) in row.iter().enumerate() { 125 | match cell { 126 | Some(cell) => { 127 | let cell_name = cell_offsets_to_index(row_index, col_index); 128 | let mut inner_cell = InnerCell::new(cell_name, &cell.s); 129 | 130 | match &cell.v { 131 | Some(value) => { 132 | if !value.is_empty() { 133 | match value.parse::() { 134 | Ok(_) if !value.starts_with("0") || value.len() == 1 => { 135 | inner_cell.value = CellValue::Value(value.to_owned()); 136 | }, 137 | Err(_) | _ => { 138 | if value.starts_with("=") { 139 | // [FIXME] formula can be corrupted 140 | inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures)); 141 | } else { 142 | shared_strings_count += 1; 143 | // [FIXME] N^2, slow 144 | match shared_strings.iter().position(|s| s == value) { 145 | Some(index) => { 146 | inner_cell.value = CellValue::SharedString(index as u32); 147 | }, 148 | None => { 149 | inner_cell.value = CellValue::SharedString(shared_strings.len() as u32); 150 | shared_strings.push(value.to_owned()); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | None => () 159 | } 160 | 161 | inner_row.push(inner_cell); 162 | }, 163 | None => () 164 | } 165 | } 166 | rows.push(inner_row); 167 | } 168 | }, 169 | None => { 170 | match &sheet.plain { 171 | Some(plain) => { 172 | for (row_index, row) in plain.iter().enumerate() { 173 | let mut inner_row: Vec = vec!(); 174 | for (col_index, cell) in row.iter().enumerate() { 175 | match cell { 176 | Some(value) => { 177 | let cell_name = cell_offsets_to_index(row_index, col_index); 178 | let mut inner_cell = InnerCell::new(cell_name, &None); 179 | match value.parse::() { 180 | Ok(_) if !value.starts_with("0") || value.len() == 1 => { 181 | inner_cell.value = CellValue::Value(value.to_owned()); 182 | }, 183 | Err(_) | _ => { 184 | if value.starts_with("=") { 185 | // [FIXME] formula can be corrupted 186 | inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures)); 187 | } else { 188 | inner_cell.value = CellValue::SharedString(shared_strings.len() as u32); 189 | shared_strings.push(value.to_owned()); 190 | } 191 | } 192 | } 193 | inner_row.push(inner_cell); 194 | }, 195 | None => () 196 | } 197 | } 198 | rows.push(inner_row); 199 | } 200 | }, 201 | None => () 202 | } 203 | } 204 | } 205 | 206 | let sheet_info = get_sheet_info(sheet.name.clone(), sheet_index); 207 | zip.start_file(sheet_info.0.clone(), options).unwrap(); 208 | zip.write_all(get_sheet_data(rows, &sheet.cols, &sheet.rows, &sheet.merged).as_bytes()).unwrap(); 209 | sheets_info.push(sheet_info); 210 | } 211 | 212 | zip.start_file("_rels/.rels", options).unwrap(); 213 | zip.write_all(ROOT_RELS).unwrap(); 214 | 215 | let (content_types, rels, workbook) = get_nav(sheets_info); 216 | 217 | zip.start_file("[Content_Types].xml", options).unwrap(); 218 | zip.write_all(content_types.as_bytes()).unwrap(); 219 | 220 | zip.start_file("xl/_rels/workbook.xml.rels", options).unwrap(); 221 | zip.write_all(rels.as_bytes()).unwrap(); 222 | zip.start_file("xl/workbook.xml", options).unwrap(); 223 | zip.write_all(workbook.as_bytes()).unwrap(); 224 | zip.start_file("xl/sharedStrings.xml", options).unwrap(); 225 | zip.write_all(get_shared_strings_data(shared_strings, shared_strings_count).as_bytes()).unwrap(); 226 | 227 | zip.start_file("xl/styles.xml", options).unwrap(); 228 | zip.write_all(get_styles_data(style_table).as_bytes()).unwrap(); 229 | 230 | let res = zip.finish().unwrap(); 231 | res.get_ref().to_vec() 232 | } 233 | 234 | fn get_styles_data(style_table: StyleTable) -> String { 235 | let mut style_sheet = Element::new("styleSheet"); 236 | 237 | let mut formats_element = Element::new("numFmts"); 238 | let mut formats_children = vec!(); 239 | for (format, index) in &style_table.custom_formats { 240 | let mut format_element = Element::new("numFmt"); 241 | format_element 242 | .add_attr("formatCode", format) 243 | .add_attr("numFmtId", index.to_string()); 244 | formats_children.push(format_element); 245 | } 246 | formats_element 247 | .add_attr("count", style_table.custom_formats.len().to_string()) 248 | .add_children(formats_children); 249 | 250 | let mut fonts_element = Element::new("fonts"); 251 | let fonts_children: Vec = style_table.fonts.iter().map(|ref font| { 252 | let mut font_element = Element::new("font"); 253 | let mut font_children = vec!(); 254 | match font.name { 255 | Some(ref font) => { 256 | let mut name_el = Element::new("name"); 257 | name_el.add_attr("val", font); 258 | font_children.push(name_el); 259 | } 260 | None => (), 261 | } 262 | match font.size { 263 | Some(ref size) => { 264 | let mut sz = Element::new("sz"); 265 | sz.add_attr("val", size.to_string()); 266 | font_children.push(sz); 267 | }, 268 | None => () 269 | } 270 | match font.color { 271 | Some(ref color) => { 272 | let mut color_element = Element::new("color"); 273 | color_element.add_attr("rgb", color); 274 | font_children.push(color_element); 275 | }, 276 | None => () 277 | } 278 | if font.bold { 279 | let b = Element::new("b"); 280 | font_children.push(b); 281 | } 282 | if font.italic { 283 | let i = Element::new("i"); 284 | font_children.push(i); 285 | } 286 | if font.underline { 287 | let u = Element::new("u"); 288 | font_children.push(u); 289 | } 290 | if font.strike { 291 | let s = Element::new("strike"); 292 | font_children.push(s); 293 | } 294 | font_element.add_children(font_children); 295 | font_element 296 | }).collect(); 297 | 298 | fonts_element 299 | .add_attr("count", style_table.fonts.len().to_string()) 300 | .add_children(fonts_children); 301 | 302 | let mut borders_element = Element::new("borders"); 303 | let border_children: Vec = style_table.borders.iter().map(|border| { 304 | let mut border_element = Element::new("border"); 305 | let mut children: Vec = vec![]; 306 | 307 | if let Some(b) = &border.left { 308 | children.push(b.to_xml_el()) 309 | } 310 | if let Some(b) = &border.right { 311 | children.push(b.to_xml_el()) 312 | } 313 | if let Some(b) = &border.top { 314 | children.push(b.to_xml_el()) 315 | } 316 | if let Some(b) = &border.bottom { 317 | children.push(b.to_xml_el()) 318 | } 319 | 320 | border_element.add_children(children); 321 | border_element 322 | }).collect(); 323 | borders_element.add_attr("count", border_children.len().to_string()); 324 | borders_element.add_children(vec![Element::new("border")]); 325 | borders_element.add_children(border_children); 326 | 327 | 328 | let mut fills_element = Element::new("fills"); 329 | let fills_children: Vec = style_table.fills.iter().map(|ref fill| { 330 | let mut fill_element = Element::new("fill"); 331 | let mut pattern_fill = Element::new("patternFill"); 332 | pattern_fill.add_attr("patternType", &fill.pattern_type); 333 | fill.color.clone().map(|color| { 334 | let mut fg_color = Element::new("fgColor"); 335 | let mut bg_color = Element::new("bgColor"); 336 | fg_color.add_attr("rgb", color.clone()); 337 | bg_color.add_attr("rgb", color.clone()); 338 | pattern_fill.add_children(vec![fg_color, bg_color]); 339 | }); 340 | fill_element.add_children(vec![pattern_fill]); 341 | fill_element 342 | }).collect(); 343 | 344 | fills_element 345 | .add_attr("count", style_table.fills.len().to_string()) 346 | .add_children(fills_children); 347 | 348 | let mut cell_xfs = Element::new("cellXfs"); 349 | let xfs_children: Vec = style_table.xfs.iter().map(|p| { 350 | let mut xf = Element::new("xf"); 351 | 352 | p.font_id.map(|id|{ 353 | xf 354 | .add_attr("applyFont", "1") 355 | .add_attr("fontId", id.to_string()); 356 | }); 357 | p.fill_id.map(|id| { 358 | xf 359 | .add_attr("applyFill", "1") 360 | .add_attr("fillId", id.to_string()); 361 | }); 362 | p.format_id.map(|id| { 363 | xf 364 | .add_attr("applyNumberFormat", "1") 365 | .add_attr("numFmtId", id.to_string()); 366 | }); 367 | p.border_id.map(|id| { 368 | xf 369 | .add_attr("applyBorder", "1") 370 | .add_attr("borderId", id.to_string()); 371 | }); 372 | if p.align_h.is_some() || p.align_v.is_some() { 373 | let mut alignment = Element::new("alignment"); 374 | p.align_h.as_ref().map(|v| alignment.add_attr("horizontal", v)); 375 | p.align_v.as_ref().map(|v| alignment.add_attr("vertical", v)); 376 | xf.add_attr("applyAlignment", "1").add_children(vec![alignment]); 377 | } 378 | 379 | xf 380 | }).collect(); 381 | 382 | cell_xfs 383 | .add_attr("count", style_table.xfs.len().to_string()) 384 | .add_children(xfs_children); 385 | 386 | style_sheet 387 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 388 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 389 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 390 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 391 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 392 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 393 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 394 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 395 | .add_children(vec![formats_element, fonts_element, fills_element, borders_element, cell_xfs]); 396 | 397 | style_sheet.to_xml() 398 | } 399 | 400 | fn cell_offsets_to_index(row: usize, col: usize) -> String { 401 | let mut num = col; 402 | let mut chars = vec!(); 403 | 404 | while num > 25 { 405 | let part = num % 26; 406 | num = (num - part) / 26 - 1; 407 | chars.push(65u8 + part as u8); 408 | } 409 | chars.push(65u8 + num as u8); 410 | chars.reverse(); 411 | format!("{}{}", String::from_utf8(chars).unwrap(), row + 1) 412 | } 413 | 414 | fn get_sheet_data(cells: Vec>, columns: &Option>>, rows: &Option>>, merged: &Option>) -> String { 415 | let mut worksheet = Element::new("worksheet"); 416 | let mut sheet_view = Element::new("sheetView"); 417 | sheet_view.add_attr("workbookViewId", "0"); 418 | let mut sheet_views = Element::new("sheetViews"); 419 | sheet_views.add_children(vec![sheet_view]); 420 | let mut sheet_format_pr = Element::new("sheetFormatPr"); 421 | sheet_format_pr 422 | .add_attr("customHeight", "1") 423 | .add_attr("defaultRowHeight", "15.75") 424 | .add_attr("defaultColWidth", "14.43"); 425 | 426 | let mut cols = Element::new("cols"); 427 | let mut cols_children = vec!(); 428 | 429 | match columns { 430 | Some(columns) => { 431 | for (index, column) in columns.iter().enumerate() { 432 | match column { 433 | Some(col) => { 434 | let mut column_element = Element::new("col"); 435 | column_element 436 | .add_attr("min", (index + 1).to_string()) 437 | .add_attr("max", (index + 1).to_string()) 438 | .add_attr("customWidth", "1") 439 | .add_attr("width", (col.width / WIDTH_COEF).to_string()); 440 | cols_children.push(column_element) 441 | }, 442 | None => () 443 | } 444 | } 445 | }, 446 | None => () 447 | } 448 | let mut rows_info: HashMap = HashMap::new(); 449 | match rows { 450 | Some(rows) => { 451 | for (index, column) in rows.iter().enumerate() { 452 | match column { 453 | Some(row) => { 454 | rows_info.insert(index, row); 455 | }, 456 | None => () 457 | } 458 | } 459 | }, 460 | None => () 461 | } 462 | 463 | let mut sheet_data = Element::new("sheetData"); 464 | let mut sheet_data_rows = vec!(); 465 | for (index, row) in cells.iter().enumerate() { 466 | let mut row_el = Element::new("row"); 467 | row_el.add_attr("r", (index + 1).to_string()); 468 | match rows_info.get(&index) { 469 | Some(row_data) => { 470 | row_el 471 | .add_attr("ht", (row_data.height * HEIGHT_COEF).to_string()) 472 | .add_attr("customHeight", "1"); 473 | }, 474 | None => () 475 | } 476 | let mut row_cells = vec!(); 477 | for cell in row { 478 | let mut cell_el = Element::new("c"); 479 | cell_el.add_attr("r", &cell.cell); 480 | match &cell.value { 481 | CellValue::Value(ref v) => { 482 | let mut value_cell = Element::new("v"); 483 | value_cell.add_value(v); 484 | cell_el.add_children(vec![value_cell]); 485 | utils::log!("value {}", v) 486 | }, 487 | CellValue::Formula(ref v) => { 488 | let mut value_cell = Element::new("f"); 489 | value_cell.add_value(v); 490 | cell_el.add_children(vec![value_cell]); 491 | utils::log!("formula {}", v) 492 | }, 493 | CellValue::SharedString(ref s) => { 494 | cell_el.add_attr("t", "s"); 495 | let mut value_cell = Element::new("v"); 496 | value_cell.add_value(s.to_string()); 497 | cell_el.add_children(vec![value_cell]); 498 | }, 499 | CellValue::None => () 500 | } 501 | match &cell.style { 502 | Some(ref v) => { 503 | // style index should be incremented as zero style was prepended 504 | cell_el.add_attr("s", (v+1).to_string()); 505 | }, 506 | None => () 507 | } 508 | row_cells.push(cell_el); 509 | } 510 | 511 | row_el.add_children(row_cells); 512 | sheet_data_rows.push(row_el); 513 | } 514 | sheet_data.add_children(sheet_data_rows); 515 | 516 | let mut worksheet_children = vec![sheet_views, sheet_format_pr]; 517 | if cols_children.len() > 0{ 518 | cols.add_children(cols_children); 519 | worksheet_children.push(cols); 520 | } 521 | worksheet_children.push(sheet_data); 522 | 523 | match merged { 524 | Some(merged) => { 525 | if merged.len() > 0 { 526 | let mut merged_cells_element = Element::new("mergeCells"); 527 | merged_cells_element 528 | .add_attr("count", merged.len().to_string()) 529 | .add_children(merged.iter().map(|MergedCell {from, to}| { 530 | let p1 = cell_offsets_to_index(from.row as usize, from.column as usize); 531 | let p2 = cell_offsets_to_index(to.row as usize, to.column as usize); 532 | let cell_ref = format!("{}:{}", p1, p2); 533 | let mut merged_cell = Element::new("mergeCell"); 534 | merged_cell.add_attr("ref", cell_ref); 535 | merged_cell 536 | }).collect()); 537 | worksheet_children.push(merged_cells_element); 538 | } 539 | }, 540 | None => () 541 | } 542 | 543 | worksheet 544 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 545 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 546 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 547 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 548 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 549 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 550 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 551 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 552 | .add_children(worksheet_children); 553 | 554 | worksheet.to_xml() 555 | } 556 | 557 | fn get_shared_strings_data(shared_strings: Vec, shared_strings_count: i32) -> String { 558 | let mut sst = Element::new("sst"); 559 | let sst_children: Vec = shared_strings.iter().map(|s| { 560 | let mut t = Element::new("t"); 561 | t.add_value(s); 562 | let mut si = Element::new("si"); 563 | si.add_children(vec![t]); 564 | si 565 | }).collect(); 566 | 567 | sst 568 | .add_attr("uniqueCount", shared_strings.len().to_string()) 569 | .add_attr("count", shared_strings_count.to_string()) 570 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 571 | .add_children(sst_children); 572 | 573 | sst.to_xml() 574 | } 575 | 576 | fn get_sheet_info(name: Option, index: usize) -> (String, String) { 577 | let sheet_name = name.unwrap_or(format!("sheet{}", index + 1)); 578 | (format!("xl/worksheets/sheet{}.xml", index + 1), sheet_name) 579 | } 580 | 581 | fn get_nav(sheets: Vec<(String, String)>) -> (String, String, String) { 582 | let mut content_types = Element::new("Types"); 583 | content_types.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/content-types"); 584 | 585 | let mut overrides = vec!(); 586 | 587 | let mut default_xml = Element::new("Default"); 588 | default_xml 589 | .add_attr("ContentType", "application/xml") 590 | .add_attr("Extension", "xml"); 591 | let mut default_rels = Element::new("Default"); 592 | default_rels 593 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 594 | .add_attr("Extension", "rels"); 595 | 596 | let mut root_rels = Element::new("Override"); 597 | root_rels 598 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 599 | .add_attr("PartName", "/_rels/.rels"); 600 | let mut root_workbook_rels = Element::new("Override"); 601 | root_workbook_rels 602 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 603 | .add_attr("PartName", "/xl/_rels/workbook.xml.rels"); 604 | let mut shared_strings_rels = Element::new("Override"); 605 | shared_strings_rels 606 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml") 607 | .add_attr("PartName", "/xl/sharedStrings.xml"); 608 | let mut style_rels = Element::new("Override"); 609 | style_rels 610 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml") 611 | .add_attr("PartName", "/xl/styles.xml"); 612 | let mut workbook_rels = Element::new("Override"); 613 | workbook_rels 614 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml") 615 | .add_attr("PartName", "/xl/workbook.xml"); 616 | 617 | overrides.push(default_xml); 618 | overrides.push(default_rels); 619 | overrides.push(root_rels); 620 | overrides.push(root_workbook_rels); 621 | overrides.push(shared_strings_rels); 622 | for (path, _) in sheets.iter() { 623 | let mut sheet_rel = Element::new("Override"); 624 | sheet_rel 625 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml") 626 | .add_attr("PartName", String::from("/") + path); 627 | overrides.push(sheet_rel); 628 | } 629 | overrides.push(style_rels); 630 | overrides.push(workbook_rels); 631 | 632 | content_types.add_children(overrides); 633 | 634 | let mut relationships = Element::new("Relationships"); 635 | relationships.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); 636 | let mut relationships_children = vec!(); 637 | 638 | let mut style_relationship = Element::new("Relationship"); 639 | style_relationship 640 | .add_attr("Id", "rId1") 641 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles") 642 | .add_attr("Target", "styles.xml"); 643 | let mut shared_string_relationship = Element::new("Relationship"); 644 | shared_string_relationship 645 | .add_attr("Id", "rId2") 646 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings") 647 | .add_attr("Target", "sharedStrings.xml"); 648 | 649 | relationships_children.push(style_relationship); 650 | relationships_children.push(shared_string_relationship); 651 | 652 | let mut workbook = Element::new("workbook"); 653 | let mut workbook_children = vec!(); 654 | workbook_children.push(Element::new("workbookPr")); 655 | let mut sheets_element = Element::new("sheets"); 656 | let mut sheet_children = vec!(); 657 | 658 | let mut last_id = 3; 659 | for (index, (_, name)) in sheets.iter().enumerate() { 660 | let mut sheet_relationship = Element::new("Relationship"); 661 | sheet_relationship 662 | .add_attr("Id", format!("rId{}", last_id)) 663 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet") 664 | .add_attr("Target", format!("worksheets/sheet{}.xml", (index+1))); 665 | relationships_children.push(sheet_relationship); 666 | let mut sheet_element = Element::new("sheet"); 667 | sheet_element 668 | .add_attr("r:id", format!("rId{}", last_id)) 669 | .add_attr("name", name) 670 | .add_attr("sheetId", (index+1).to_string()); 671 | 672 | if index == 0 { 673 | sheet_element.add_attr("state", "visible"); 674 | } 675 | sheet_children.push(sheet_element); 676 | 677 | last_id += 1; 678 | } 679 | relationships.add_children(relationships_children); 680 | sheets_element.add_children(sheet_children); 681 | workbook_children.push(sheets_element); 682 | workbook_children.push(Element::new("definedNames")); 683 | workbook_children.push(Element::new("calcPr")); 684 | 685 | workbook 686 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 687 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 688 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 689 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 690 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 691 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 692 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 693 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 694 | .add_children(workbook_children); 695 | 696 | (content_types.to_xml(), relationships.to_xml(), workbook.to_xml()) 697 | } -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::xml::Element; 4 | 5 | #[derive(PartialEq)] 6 | pub struct Font { 7 | pub name: Option, 8 | pub size: Option, 9 | pub color: Option, 10 | pub bold: bool, 11 | pub italic: bool, 12 | pub underline: bool, 13 | pub strike: bool, 14 | } 15 | 16 | #[derive(PartialEq)] 17 | pub struct Fill { 18 | pub pattern_type: String, 19 | pub color: Option, 20 | } 21 | 22 | #[derive(PartialEq)] 23 | pub struct Border { 24 | pub top: Option, 25 | pub right: Option, 26 | pub bottom: Option, 27 | pub left: Option, 28 | } 29 | 30 | pub struct StyleProps { 31 | pub font: Option, 32 | pub fill: Option, 33 | pub border: Option, 34 | pub align_h: Option, 35 | pub align_v: Option, 36 | } 37 | 38 | #[derive(PartialEq, Debug)] 39 | pub enum BorderStyle { 40 | Medium, 41 | Dotted, 42 | Thick, 43 | Thin, 44 | Dashed, 45 | Double, 46 | } 47 | 48 | #[derive(PartialEq, Debug)] 49 | pub enum BorderPosition { 50 | Top, 51 | Right, 52 | Bottom, 53 | Left, 54 | } 55 | 56 | #[derive(PartialEq)] 57 | pub struct BorderProps { 58 | pub position: BorderPosition, 59 | pub size: f32, 60 | pub style: BorderStyle, 61 | pub color: String, 62 | } 63 | 64 | pub struct StyleTable { 65 | pub fonts: Vec, 66 | pub fills: Vec, 67 | pub borders: Vec, 68 | pub xfs: Vec, 69 | pub custom_formats: HashMap, 70 | next_custom_format: u32, 71 | } 72 | 73 | pub struct XFSProps { 74 | pub font_id: Option, 75 | pub border_id: Option, 76 | pub fill_id: Option, 77 | pub format_id: Option, 78 | pub align_h: Option, 79 | pub align_v: Option, 80 | } 81 | 82 | impl StyleTable { 83 | pub fn new(css: Option>>) -> StyleTable { 84 | let mut table = StyleTable { 85 | fonts: vec![Font::new()], 86 | fills: vec![Fill::new(None, "none"), Fill::new(None, "gray125")], 87 | borders: vec![Border::new()], 88 | xfs: vec![XFSProps::new()], 89 | custom_formats: HashMap::new(), 90 | next_custom_format: 165, 91 | }; 92 | table.custom_formats.insert(String::from("General"), 164); 93 | 94 | css.map(|map| { 95 | for style in map { 96 | table.add(style); 97 | } 98 | }); 99 | 100 | table 101 | } 102 | pub fn add(&mut self, style: HashMap) { 103 | let mut xsf_props: XFSProps = XFSProps::new(); 104 | let st = style_to_props(&style); 105 | 106 | xsf_props.font_id = st 107 | .font 108 | .map(|font| match self.fonts.iter().position(|f| f == &font) { 109 | Some(v) => v, 110 | None => { 111 | self.fonts.push(font); 112 | self.fonts.len() - 1 113 | } 114 | }); 115 | xsf_props.fill_id = st 116 | .fill 117 | .map(|fill| match self.fills.iter().position(|f| f == &fill) { 118 | Some(v) => v, 119 | None => { 120 | self.fills.push(fill); 121 | self.fills.len() - 1 122 | } 123 | }); 124 | xsf_props.border_id = 125 | st.border.map( 126 | |border| match self.borders.iter().position(|b| b == &border) { 127 | Some(v) => v, 128 | None => { 129 | self.borders.push(border); 130 | self.borders.len() - 1 131 | } 132 | }, 133 | ); 134 | xsf_props.align_h = st.align_h; 135 | xsf_props.align_v = st.align_v; 136 | xsf_props.format_id = style.get("format").map(|format_name| { 137 | let i = match get_format_code(format_name) { 138 | Some(format) => format, 139 | None => { 140 | if self.custom_formats.contains_key(format_name) { 141 | self.custom_formats.get(format_name).unwrap().to_owned() 142 | } else { 143 | let index = self.next_custom_format; 144 | self.custom_formats.insert(format_name.to_owned(), index); 145 | self.next_custom_format += 1; 146 | index 147 | } 148 | } 149 | }; 150 | i as usize 151 | }); 152 | 153 | self.xfs.push(xsf_props); 154 | } 155 | } 156 | 157 | impl Fill { 158 | pub fn new(color: Option, pattern_type: &str) -> Fill { 159 | Fill { 160 | pattern_type: pattern_type.to_owned(), 161 | color: color, 162 | } 163 | } 164 | } 165 | 166 | impl Font { 167 | pub fn new() -> Font { 168 | Font { 169 | name: None, 170 | size: None, 171 | color: None, 172 | bold: false, 173 | italic: false, 174 | underline: false, 175 | strike: false, 176 | } 177 | } 178 | } 179 | 180 | impl Border { 181 | pub fn new() -> Border { 182 | Border { 183 | top: None, 184 | right: None, 185 | bottom: None, 186 | left: None, 187 | } 188 | } 189 | } 190 | 191 | impl BorderProps { 192 | pub fn to_xml_el(&self) -> Element { 193 | let el_name = match self.position { 194 | BorderPosition::Top => "top", 195 | BorderPosition::Right => "right", 196 | BorderPosition::Bottom => "bottom", 197 | BorderPosition::Left => "left", 198 | }; 199 | 200 | let mut el = Element::new(el_name); 201 | 202 | let attr_style = match self.style { 203 | BorderStyle::Dotted => "dotted", 204 | BorderStyle::Thick => "thick", 205 | BorderStyle::Double => "double", 206 | BorderStyle::Dashed => "hair", 207 | BorderStyle::Medium => "medium", 208 | BorderStyle::Thin => "thin", 209 | }; 210 | 211 | el.add_attr("style", attr_style); 212 | 213 | if !self.color.is_empty() { 214 | let mut el_color = Element::new("color"); 215 | el_color.add_attr("rgb", self.color.to_owned()); 216 | el.add_children(vec![el_color]); 217 | } 218 | 219 | el 220 | } 221 | } 222 | 223 | impl StyleProps { 224 | pub fn new() -> StyleProps { 225 | StyleProps { 226 | align_h: None, 227 | fill: None, 228 | font: None, 229 | border: None, 230 | align_v: None, 231 | } 232 | } 233 | } 234 | 235 | impl XFSProps { 236 | pub fn new() -> XFSProps { 237 | XFSProps { 238 | font_id: None, 239 | fill_id: None, 240 | border_id: None, 241 | format_id: None, 242 | align_h: None, 243 | align_v: None, 244 | } 245 | } 246 | } 247 | 248 | fn style_to_props(styles: &HashMap) -> StyleProps { 249 | let mut font: Font = Font::new(); 250 | let mut border: Border = Border::new(); 251 | let mut st = StyleProps::new(); 252 | 253 | for (key, value) in styles { 254 | match key.as_ref() { 255 | "background" => match color_to_argb(value) { 256 | Some(v) => st.fill = Some(Fill::new(Some(v), "solid")), 257 | None => (), 258 | }, 259 | "color" => font.color = color_to_argb(value), 260 | "fontWeight" => font.bold = value == "bold", 261 | "fontStyle" => font.italic = value == "italic", 262 | "fontFamily" => font.name = Some(value.to_string()), 263 | "textDecoration" => { 264 | font.underline = value.contains("underline"); 265 | font.strike = value.contains("line-through"); 266 | } 267 | "fontSize" => font.size = px_to_pt(&value), 268 | "align" => st.align_h = Some(value.to_owned()), 269 | "verticalAlign" => st.align_v = Some(value.to_owned()), 270 | "borderTop" => border.top = str_to_border(&value, BorderPosition::Top), 271 | "borderRight" => border.right = str_to_border(&value, BorderPosition::Right), 272 | "borderBottom" => border.bottom = str_to_border(&value, BorderPosition::Bottom), 273 | "borderLeft" => border.left = str_to_border(&value, BorderPosition::Left), 274 | _ => (), 275 | } 276 | } 277 | 278 | st.font = Some(font); 279 | st.border = Some(border); 280 | 281 | st 282 | } 283 | 284 | fn color_to_argb(color: &str) -> Option { 285 | let len = color.len(); 286 | let mut argb_color = String::new(); 287 | if len == 4 && &color[0..1] == "#" { 288 | let hex3 = &color[1..4]; 289 | let r = &hex3[0..1]; 290 | let g = &hex3[1..2]; 291 | let b = &hex3[2..3]; 292 | let argb = format!("FF{}{}{}{}{}{}", r, r, g, g, b, b); 293 | Some(argb) 294 | } else if len == 7 && &color[0..1] == "#" { 295 | argb_color.push_str("FF"); 296 | argb_color.push_str(&color[1..]); 297 | Some(argb_color) 298 | } else if len > 11 && &color[0..5] == "rgba(" && &color[len - 1..] == ")" { 299 | let colors_part = &color[5..len - 1]; 300 | let colors = colors_part 301 | .split(",") 302 | .map(|s| s.trim()) 303 | .collect::>(); 304 | if colors.len() < 4 { 305 | return None; 306 | } 307 | let r = str_to_hex(colors[0]); 308 | let g = str_to_hex(colors[1]); 309 | let b = str_to_hex(colors[2]); 310 | let a = str_alpha_to_hex(colors[3]); 311 | if r.is_none() || g.is_none() || b.is_none() || a.is_none() { 312 | return None; 313 | } 314 | argb_color.push_str(&a.unwrap()); 315 | argb_color.push_str(&r.unwrap()); 316 | argb_color.push_str(&g.unwrap()); 317 | argb_color.push_str(&b.unwrap()); 318 | Some(argb_color) 319 | } else if len > 10 && &color[0..4] == "rgb(" && &color[len - 1..] == ")" { 320 | let colors_part = &color[4..len - 1]; 321 | let colors = colors_part 322 | .split(",") 323 | .map(|s| s.trim()) 324 | .collect::>(); 325 | if colors.len() < 3 { 326 | return None; 327 | } 328 | let r = str_to_hex(colors[0]); 329 | let g = str_to_hex(colors[1]); 330 | let b = str_to_hex(colors[2]); 331 | if r.is_none() || g.is_none() || b.is_none() { 332 | return None; 333 | } 334 | argb_color.push_str("FF"); 335 | argb_color.push_str(&r.unwrap()); 336 | argb_color.push_str(&g.unwrap()); 337 | argb_color.push_str(&b.unwrap()); 338 | Some(argb_color) 339 | } else { 340 | None 341 | } 342 | } 343 | 344 | fn str_to_hex(s: &str) -> Option { 345 | match s.parse::() { 346 | Ok(v) => { 347 | let res = format!("{:X}", v); 348 | match res.len() { 349 | 1 => Some(String::from("0") + &res), 350 | 2 => Some(res), 351 | _ => None, 352 | } 353 | } 354 | Err(_) => None, 355 | } 356 | } 357 | 358 | fn str_alpha_to_hex(s: &str) -> Option { 359 | match s.parse::() { 360 | Ok(v) => { 361 | let res = format!("{:X}", (v * 255f32) as u32); 362 | match res.len() { 363 | 1 => Some(String::from("0") + &res), 364 | 2 => Some(res), 365 | _ => None, 366 | } 367 | } 368 | Err(_) => None, 369 | } 370 | } 371 | 372 | fn px_to_pt(size: &str) -> Option { 373 | let len = size.len(); 374 | if &size[len - 2..].to_owned() != "px" { 375 | None 376 | } else { 377 | match size[0..len - 2].to_owned().parse::() { 378 | Ok(v) => Some((v * 0.75).to_string()), 379 | Err(_) => None, 380 | } 381 | } 382 | } 383 | 384 | fn get_format_code(format: &str) -> Option { 385 | match format { 386 | "" | "General" => Some(0), 387 | "0" => Some(1), 388 | "0.00" => Some(2), 389 | "#,##0" => Some(3), 390 | "#,##0.00" => Some(4), 391 | "0%" => Some(9), 392 | "0.00%" => Some(10), 393 | "0.00E+00" => Some(11), 394 | "# ?/?" => Some(12), 395 | "# ??/??" => Some(13), 396 | "mm-dd-yy" => Some(14), 397 | "d-mmm-yy" => Some(15), 398 | "d-mmm" => Some(16), 399 | "mmm-yy" => Some(17), 400 | "h:mm AM/PM" => Some(18), 401 | "h:mm:ss AM/PM" => Some(19), 402 | "h:mm" => Some(20), 403 | "h:mm:ss" => Some(21), 404 | "m/d/yy h:mm" => Some(22), 405 | "#,##0 ;(#,##0)" => Some(37), 406 | "#,##0 ;[Red](#,##0)" => Some(38), 407 | "#,##0.00;[Red](#,##0.00)" => Some(40), 408 | "mm:ss" => Some(45), 409 | "[h]:mm:ss" => Some(46), 410 | "mmss.0" => Some(47), 411 | "##0.0E+0" => Some(48), 412 | "@" => Some(49), 413 | _ => None, 414 | } 415 | } 416 | 417 | fn str_to_border(v: &str, pos: BorderPosition) -> Option { 418 | let parts = v.split(" "); 419 | let vals: Vec<&str> = parts.collect(); 420 | 421 | if vals.len() != 3 { 422 | return None; 423 | } 424 | 425 | let size = match vals[0].to_string().trim_end_matches("px").parse::() { 426 | Ok(s) => s, 427 | Err(_) => return None, 428 | }; 429 | 430 | let style = match vals[1] { 431 | "thin" => BorderStyle::Thin, 432 | "dotted" => BorderStyle::Dotted, 433 | "double" => BorderStyle::Double, 434 | "dashed" => BorderStyle::Dashed, 435 | "solid" => { 436 | let mut st = BorderStyle::Thin; 437 | 438 | if size == 0.5 { 439 | st = BorderStyle::Thin 440 | } else if size == 1.0 { 441 | st = BorderStyle::Medium 442 | } else if size == 2.0 { 443 | st = BorderStyle::Thick 444 | } 445 | 446 | st 447 | } 448 | _ => return None, 449 | }; 450 | 451 | let color: String = match color_to_argb(vals[2]) { 452 | Some(s) => s, 453 | None => return None, 454 | }; 455 | 456 | Some(BorderProps { 457 | position: pos, 458 | size: size, 459 | style: style, 460 | color: color, 461 | }) 462 | } 463 | 464 | #[test] 465 | fn style_to_props_test() { 466 | let mut styles: HashMap = HashMap::new(); 467 | styles.insert(String::from("background"), String::from("#FF0000")); 468 | styles.insert(String::from("color"), String::from("#FFFF00")); 469 | styles.insert(String::from("fontWeight"), String::from("bold")); 470 | styles.insert(String::from("fontStyle"), String::from("italic")); 471 | styles.insert(String::from("fontSize"), String::from("24px")); 472 | styles.insert(String::from("fontFamily"), String::from("Calibri")); 473 | styles.insert(String::from("textDecoration"), String::from("underline")); 474 | styles.insert(String::from("align"), String::from("left")); 475 | styles.insert(String::from("verticalAlign"), String::from("bottom")); 476 | styles.insert(String::from("borderTop"), String::from("1px solid #9AFF02")); 477 | styles.insert( 478 | String::from("borderRight"), 479 | String::from("1px solid #000000"), 480 | ); 481 | 482 | let st = style_to_props(&styles); 483 | 484 | let font = st.font.unwrap(); 485 | assert_eq!(font.size, Some(String::from("18"))); 486 | assert_eq!(font.color, Some(String::from("FFFFFF00"))); 487 | assert_eq!(font.bold, true); 488 | assert_eq!(font.name, Some(String::from("Calibri"))); 489 | assert_eq!(font.italic, true); 490 | assert_eq!(font.underline, true); 491 | assert_eq!(st.fill.unwrap().color, Some(String::from("FFFF0000"))); 492 | assert_eq!(st.align_h, Some(String::from("left"))); 493 | assert_eq!(st.align_v, Some(String::from("bottom"))); 494 | 495 | let border = st.border.unwrap(); 496 | assert_eq!(border.top.unwrap().color, String::from("FF9AFF02")); 497 | assert_eq!(border.right.unwrap().color, String::from("FF000000")); 498 | } 499 | 500 | #[test] 501 | fn str_to_hex_test() { 502 | assert_eq!(str_to_hex("255"), Some(String::from("FF"))); 503 | assert_eq!(str_alpha_to_hex("0.5"), Some(String::from("7F"))); 504 | } 505 | 506 | #[test] 507 | fn color_to_argb_test() { 508 | assert_eq!( 509 | color_to_argb("rgba(255, 255, 255, 1)"), 510 | Some(String::from("FFFFFFFF")) 511 | ); 512 | assert_eq!( 513 | color_to_argb("rgb(254,254,254)"), 514 | Some(String::from("FFFEFEFE")) 515 | ); 516 | assert_eq!(color_to_argb("#FF6347"), Some(String::from("FFFF6347"))); 517 | assert_eq!(color_to_argb("#A4B"), Some(String::from("FFAA44BB"))); 518 | } 519 | 520 | #[test] 521 | fn str_to_border_test() { 522 | let mut maybe_border = str_to_border("1px solid #9900CC", BorderPosition::Top); 523 | let mut b = maybe_border.unwrap(); 524 | assert_eq!(b.color, "FF9900CC"); 525 | assert_eq!(b.position, BorderPosition::Top); 526 | assert_eq!(b.size, 1.0); 527 | assert_eq!(b.style, BorderStyle::Medium); 528 | 529 | maybe_border = str_to_border("0.5px solid #B3FFB3", BorderPosition::Right); 530 | b = maybe_border.unwrap(); 531 | assert_eq!(b.color, "FFB3FFB3"); 532 | assert_eq!(b.position, BorderPosition::Right); 533 | assert_eq!(b.size, 0.5); 534 | assert_eq!(b.style, BorderStyle::Thin); 535 | 536 | maybe_border = str_to_border("2px solid #BCD", BorderPosition::Bottom); 537 | b = maybe_border.unwrap(); 538 | assert_eq!(b.color, "FFBBCCDD"); 539 | assert_eq!(b.position, BorderPosition::Bottom); 540 | assert_eq!(b.size, 2.0); 541 | assert_eq!(b.style, BorderStyle::Thick); 542 | 543 | maybe_border = str_to_border("2px dashed #BCD", BorderPosition::Left); 544 | b = maybe_border.unwrap(); 545 | assert_eq!(b.style, BorderStyle::Dashed); 546 | 547 | maybe_border = str_to_border("2px dotted #BCD", BorderPosition::Left); 548 | b = maybe_border.unwrap(); 549 | assert_eq!(b.style, BorderStyle::Dotted); 550 | 551 | maybe_border = str_to_border("2px double #BCD", BorderPosition::Left); 552 | b = maybe_border.unwrap(); 553 | assert_eq!(b.style, BorderStyle::Double); 554 | } 555 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | extern crate web_sys; 2 | 3 | pub fn set_panic_hook() { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | #[cfg(feature = "console_error_panic_hook")] 11 | console_error_panic_hook::set_once(); 12 | } 13 | 14 | // A macro to provide `println!(..)`-style syntax for `console.log` logging. 15 | macro_rules! log { 16 | ( $( $t:tt )* ) => { 17 | web_sys::console::log_1(&format!( $( $t )* ).into()) 18 | } 19 | } 20 | pub(crate) use log; 21 | -------------------------------------------------------------------------------- /src/xml.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | struct Attr<'a>(Cow<'a, str>, Cow<'a, str>); 4 | 5 | pub struct Element<'a> { 6 | tag: Cow<'a, str>, 7 | attributes: Vec>, 8 | content: Content<'a> 9 | } 10 | 11 | enum Content<'a> { 12 | Empty, 13 | Value(Cow<'a, str>), 14 | Children(Vec>) 15 | } 16 | 17 | impl<'a> Element<'a> { 18 | pub fn new(tag: S) -> Element<'a> where S: Into> { 19 | Element { 20 | tag: tag.into(), 21 | attributes: vec!(), 22 | content: Content::Empty 23 | } 24 | } 25 | pub fn add_attr(&mut self, name: S, value: T) -> &mut Self where S: Into>, T: Into> { 26 | self.attributes.push(Attr(name.into(), to_safe_attr_value(value.into()))); 27 | self 28 | } 29 | pub fn add_value(&mut self, value: S) where S: Into> { 30 | self.content = Content::Value(to_safe_string(value.into())); 31 | } 32 | pub fn add_children(&mut self, children: Vec>) { 33 | if children.len() != 0 { 34 | self.content = Content::Children(children); 35 | } 36 | } 37 | pub fn to_xml(&mut self) -> String { 38 | let mut result = String::new(); 39 | result.push_str(r#""#); 40 | result.push_str(&self.to_string()); 41 | result 42 | } 43 | } 44 | 45 | impl<'a> ToString for Element<'a> { 46 | fn to_string(&self) -> String { 47 | let mut attrs = String::new(); 48 | for Attr(name, value) in &self.attributes { 49 | let attr = format!(" {}=\"{}\"", name, value); 50 | attrs.push_str(&attr); 51 | } 52 | let mut result = String::new(); 53 | match self.content { 54 | Content::Empty => { 55 | let tag = format!("<{}{}/>", self.tag, attrs); 56 | result.push_str(&tag); 57 | }, 58 | Content::Value(ref v) => { 59 | let tag = format!("<{t}{a}>{c}", t = self.tag, a = attrs, c = v); 60 | result.push_str(&tag); 61 | }, 62 | Content::Children(ref c) => { 63 | let mut children = String::new(); 64 | for element in c { 65 | children.push_str(&element.to_string()); 66 | } 67 | let tag = format!("<{t}{a}>{c}", t = self.tag, a = attrs, c = children); 68 | result.push_str(&tag); 69 | } 70 | } 71 | result 72 | } 73 | } 74 | 75 | fn to_safe_attr_value<'a>(input: Cow<'a, str>) -> Cow<'a, str> { 76 | let mut result = String::new(); 77 | for c in input.chars() { 78 | match c { 79 | '"' => result.push_str("""), 80 | _ => result.push(c) 81 | } 82 | } 83 | Cow::from(result) 84 | } 85 | 86 | fn to_safe_string<'a>(input: Cow<'a, str>) -> Cow<'a, str> { 87 | let mut result = String::new(); 88 | for c in input.chars() { 89 | match c { 90 | '<' => result.push_str("<"), 91 | '>' => result.push_str(">"), 92 | '&' => result.push_str("&"), 93 | _ => result.push(c) 94 | } 95 | } 96 | Cow::from(result) 97 | } 98 | 99 | #[test] 100 | fn element_test() { 101 | let mut el = Element::new("test"); 102 | el 103 | .add_attr("val", "42") 104 | .add_attr("val2", "42") 105 | .add_value("inner 42"); 106 | 107 | assert_eq!(el.to_string(), r#"inner 42"#); 108 | } 109 | #[test] 110 | fn element_inner_test() { 111 | let mut root = Element::new("root"); 112 | root.add_attr("isroot", "true"); 113 | 114 | let child1 = Element::new("child1"); 115 | let mut child2 = Element::new("child2"); 116 | 117 | let child2_1 = Element::new("child2_1"); 118 | let child2_2 = Element::new("child2_2"); 119 | 120 | child2.add_children(vec![child2_1, child2_2]); 121 | root.add_children(vec![child1, child2]); 122 | 123 | assert_eq!(root.to_string(), r#""#); 124 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig } from 'vite' 3 | import wasm from "vite-plugin-wasm"; 4 | import topLevelAwait from "vite-plugin-top-level-await"; 5 | 6 | const baseConfig = { 7 | base: "./", 8 | plugins: [ 9 | wasm(), 10 | topLevelAwait() 11 | ], 12 | build: { 13 | target: "esnext", 14 | emptyOutDir: false, 15 | } 16 | }; 17 | 18 | // Use mode to determine which config to use 19 | export default defineConfig(({ mode }) => { 20 | if (mode === 'module') { 21 | return { 22 | ...baseConfig, 23 | build: { 24 | ...baseConfig.build, 25 | lib: { 26 | entry: resolve(__dirname, "./js/module.js"), 27 | formats: ['es'], 28 | fileName: () => 'module.js', 29 | }, 30 | rollupOptions: { 31 | output: { 32 | entryFileNames: `[name].js`, 33 | chunkFileNames: `[name].js`, 34 | assetFileNames: `[name].[ext]` 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | if (mode === 'worker') { 42 | return { 43 | ...baseConfig, 44 | build: { 45 | ...baseConfig.build, 46 | lib: { 47 | entry: resolve(__dirname, "./js/worker.js"), 48 | formats: ['cjs'], 49 | fileName: () => 'worker.js', 50 | }, 51 | rollupOptions: { 52 | output: { 53 | entryFileNames: `[name].js`, 54 | chunkFileNames: `[name].js`, 55 | assetFileNames: `[name].[ext]` 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | throw new Error('Please specify mode: module or worker'); 63 | }); -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/android-arm64@0.17.17": 6 | version "0.17.17" 7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31" 8 | integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg== 9 | 10 | "@esbuild/android-arm@0.17.17": 11 | version "0.17.17" 12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065" 13 | integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg== 14 | 15 | "@esbuild/android-x64@0.17.17": 16 | version "0.17.17" 17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f" 18 | integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA== 19 | 20 | "@esbuild/darwin-arm64@0.17.17": 21 | version "0.17.17" 22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196" 23 | integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ== 24 | 25 | "@esbuild/darwin-x64@0.17.17": 26 | version "0.17.17" 27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20" 28 | integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg== 29 | 30 | "@esbuild/freebsd-arm64@0.17.17": 31 | version "0.17.17" 32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87" 33 | integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA== 34 | 35 | "@esbuild/freebsd-x64@0.17.17": 36 | version "0.17.17" 37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b" 38 | integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw== 39 | 40 | "@esbuild/linux-arm64@0.17.17": 41 | version "0.17.17" 42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6" 43 | integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw== 44 | 45 | "@esbuild/linux-arm@0.17.17": 46 | version "0.17.17" 47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b" 48 | integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg== 49 | 50 | "@esbuild/linux-ia32@0.17.17": 51 | version "0.17.17" 52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212" 53 | integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q== 54 | 55 | "@esbuild/linux-loong64@0.17.17": 56 | version "0.17.17" 57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40" 58 | integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw== 59 | 60 | "@esbuild/linux-mips64el@0.17.17": 61 | version "0.17.17" 62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b" 63 | integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A== 64 | 65 | "@esbuild/linux-ppc64@0.17.17": 66 | version "0.17.17" 67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c" 68 | integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ== 69 | 70 | "@esbuild/linux-riscv64@0.17.17": 71 | version "0.17.17" 72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f" 73 | integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA== 74 | 75 | "@esbuild/linux-s390x@0.17.17": 76 | version "0.17.17" 77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade" 78 | integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ== 79 | 80 | "@esbuild/linux-x64@0.17.17": 81 | version "0.17.17" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79" 83 | integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg== 84 | 85 | "@esbuild/netbsd-x64@0.17.17": 86 | version "0.17.17" 87 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c" 88 | integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA== 89 | 90 | "@esbuild/openbsd-x64@0.17.17": 91 | version "0.17.17" 92 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff" 93 | integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA== 94 | 95 | "@esbuild/sunos-x64@0.17.17": 96 | version "0.17.17" 97 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5" 98 | integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q== 99 | 100 | "@esbuild/win32-arm64@0.17.17": 101 | version "0.17.17" 102 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54" 103 | integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q== 104 | 105 | "@esbuild/win32-ia32@0.17.17": 106 | version "0.17.17" 107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e" 108 | integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ== 109 | 110 | "@esbuild/win32-x64@0.17.17": 111 | version "0.17.17" 112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9" 113 | integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg== 114 | 115 | "@rollup/plugin-virtual@^3.0.1": 116 | version "3.0.1" 117 | resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.1.tgz#cea7e489481cc0ca91516c047f8c53c1cfb1adf6" 118 | integrity sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og== 119 | 120 | "@swc/core-darwin-arm64@1.3.51": 121 | version "1.3.51" 122 | resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.51.tgz#fa799937e64e019f3aa4346ed1d039d55042cfc3" 123 | integrity sha512-DM15fJgaXQ+BOoTlMCBoRBSzkpC2V8vAXaAvh3BZ+BI6/03FUQ0j9CMIaSkss3VOv+WwqzllmcT71C/oVDQ7Tg== 124 | 125 | "@swc/core-darwin-x64@1.3.51": 126 | version "1.3.51" 127 | resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.51.tgz#3bdf5709ffd6ee9a49bf8175511997b946b7d327" 128 | integrity sha512-EPAneufZfFQUkpkf2m8Ap8TajLvjWI+UmDQz54QaofLaigXgrnLoqTtnZHBfDbUTApGYz3GaqjfZ2fMLGiISLQ== 129 | 130 | "@swc/core-linux-arm-gnueabihf@1.3.51": 131 | version "1.3.51" 132 | resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.51.tgz#ec5e41a2e9794a0cc915916bc2ed05b2fb587df5" 133 | integrity sha512-sASxO3lJjlY5g8S25yCQirDOW6zqBNeDSUCBrulaVxttx0PcL64kc6qaOlM3HKlNO4W1P7RW/mGFR4bBov+yIg== 134 | 135 | "@swc/core-linux-arm64-gnu@1.3.51": 136 | version "1.3.51" 137 | resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.51.tgz#3604c3ef5b4a4e8adc20c046c6ebb80f671bb1b0" 138 | integrity sha512-z8yHRUK+5mRxSQkw9uND8QSt8lTrW0X8blmP12Q7c7RKWOHqIaGS60a3VvLuTal7k48K4YTstSevIrGwGK88sA== 139 | 140 | "@swc/core-linux-arm64-musl@1.3.51": 141 | version "1.3.51" 142 | resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.51.tgz#6aedabc8223e6f1a2ee8d20ad659782f595b5af8" 143 | integrity sha512-lMlp09lv6qDURvETw4AAZAjaJfvjwHjiAuB+JuZrgP3zdxB21M6cMas3EjAGXtNabpU1FJu+8Lsys6/GBBjsPQ== 144 | 145 | "@swc/core-linux-x64-gnu@1.3.51": 146 | version "1.3.51" 147 | resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.51.tgz#790733e385260c7222a9328b30580e6d6227edba" 148 | integrity sha512-6zK4tDr6do6RFTJv38Rb8ZjBLdfSN7GeuyOJpblz1Qu62RqyY2Zf3fxuCZY9tkoEepZ0MvU0d4D7HhAUYKj20A== 149 | 150 | "@swc/core-linux-x64-musl@1.3.51": 151 | version "1.3.51" 152 | resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.51.tgz#ef62129e963b92af460d37d652845cfb4c503aa7" 153 | integrity sha512-ZwW+X9XdEiAszX+zfaLdOVfi5rQP3vnVwuNAiuX9eq5jHdfOKfKaNtJaGTD8w8NgMavaBM5AMaCHshFVNF0vRw== 154 | 155 | "@swc/core-win32-arm64-msvc@1.3.51": 156 | version "1.3.51" 157 | resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.51.tgz#f38c07bacfa1ace04d41bb4f3109458305e55823" 158 | integrity sha512-w+IX4xCIZH6RQG7RrOOrrHqIqM7JIj9BDZHM9LAYC5MIbDinwjnSUXz7bpn0L1LRusvPtmbTulLuSkmVBSSwAg== 159 | 160 | "@swc/core-win32-ia32-msvc@1.3.51": 161 | version "1.3.51" 162 | resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.51.tgz#39c97bff1a249f3f77786d61debab82744b2dc9a" 163 | integrity sha512-Bzv/h0HkoKkTWOOoHtehId/6AS5hLBbWE5czzcQc8SWs+BNNV8zjWoq1oYn7/gLLEhdKaBAxv9q7RHzOfBx28A== 164 | 165 | "@swc/core-win32-x64-msvc@1.3.51": 166 | version "1.3.51" 167 | resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.51.tgz#7863dc645e149b92df85ce6fa872bee44a24f540" 168 | integrity sha512-dTKAdSd0e2Sfz3Sl3m6RGLQbk6jdSIh8TlFomF4iiHDHq4PxLTzjaOVvKUAP5wux9DtBnAgZeSHMuQfM4aL9oA== 169 | 170 | "@swc/core@^1.3.10": 171 | version "1.3.51" 172 | resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.51.tgz#612de4c9be8d7237f74521ed9044db6b540c5e26" 173 | integrity sha512-/fdKlrs2NacLeOKrVZjCPfw5GeUIyBcJg0GDBn0+qwC3Y6k85m4aswK1sfRDF3nzyeXXoBr7YBb+/cSdFq9pVw== 174 | optionalDependencies: 175 | "@swc/core-darwin-arm64" "1.3.51" 176 | "@swc/core-darwin-x64" "1.3.51" 177 | "@swc/core-linux-arm-gnueabihf" "1.3.51" 178 | "@swc/core-linux-arm64-gnu" "1.3.51" 179 | "@swc/core-linux-arm64-musl" "1.3.51" 180 | "@swc/core-linux-x64-gnu" "1.3.51" 181 | "@swc/core-linux-x64-musl" "1.3.51" 182 | "@swc/core-win32-arm64-msvc" "1.3.51" 183 | "@swc/core-win32-ia32-msvc" "1.3.51" 184 | "@swc/core-win32-x64-msvc" "1.3.51" 185 | 186 | esbuild@^0.17.5: 187 | version "0.17.17" 188 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.17.tgz#fa906ab11b11d2ed4700f494f4f764229b25c916" 189 | integrity sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA== 190 | optionalDependencies: 191 | "@esbuild/android-arm" "0.17.17" 192 | "@esbuild/android-arm64" "0.17.17" 193 | "@esbuild/android-x64" "0.17.17" 194 | "@esbuild/darwin-arm64" "0.17.17" 195 | "@esbuild/darwin-x64" "0.17.17" 196 | "@esbuild/freebsd-arm64" "0.17.17" 197 | "@esbuild/freebsd-x64" "0.17.17" 198 | "@esbuild/linux-arm" "0.17.17" 199 | "@esbuild/linux-arm64" "0.17.17" 200 | "@esbuild/linux-ia32" "0.17.17" 201 | "@esbuild/linux-loong64" "0.17.17" 202 | "@esbuild/linux-mips64el" "0.17.17" 203 | "@esbuild/linux-ppc64" "0.17.17" 204 | "@esbuild/linux-riscv64" "0.17.17" 205 | "@esbuild/linux-s390x" "0.17.17" 206 | "@esbuild/linux-x64" "0.17.17" 207 | "@esbuild/netbsd-x64" "0.17.17" 208 | "@esbuild/openbsd-x64" "0.17.17" 209 | "@esbuild/sunos-x64" "0.17.17" 210 | "@esbuild/win32-arm64" "0.17.17" 211 | "@esbuild/win32-ia32" "0.17.17" 212 | "@esbuild/win32-x64" "0.17.17" 213 | 214 | fsevents@~2.3.2: 215 | version "2.3.2" 216 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 217 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 218 | 219 | function-bind@^1.1.1: 220 | version "1.1.1" 221 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 222 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 223 | 224 | has@^1.0.3: 225 | version "1.0.3" 226 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 227 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 228 | dependencies: 229 | function-bind "^1.1.1" 230 | 231 | is-core-module@^2.11.0: 232 | version "2.12.0" 233 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" 234 | integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== 235 | dependencies: 236 | has "^1.0.3" 237 | 238 | nanoid@^3.3.6: 239 | version "3.3.6" 240 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" 241 | integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== 242 | 243 | path-parse@^1.0.7: 244 | version "1.0.7" 245 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 246 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 247 | 248 | picocolors@^1.0.0: 249 | version "1.0.0" 250 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 251 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 252 | 253 | postcss@^8.4.21: 254 | version "8.4.22" 255 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.22.tgz#c29e6776b60ab3af602d4b513d5bd2ff9aa85dc1" 256 | integrity sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA== 257 | dependencies: 258 | nanoid "^3.3.6" 259 | picocolors "^1.0.0" 260 | source-map-js "^1.0.2" 261 | 262 | resolve@^1.22.1: 263 | version "1.22.2" 264 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" 265 | integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== 266 | dependencies: 267 | is-core-module "^2.11.0" 268 | path-parse "^1.0.7" 269 | supports-preserve-symlinks-flag "^1.0.0" 270 | 271 | rollup@^3.18.0: 272 | version "3.20.6" 273 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd" 274 | integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw== 275 | optionalDependencies: 276 | fsevents "~2.3.2" 277 | 278 | source-map-js@^1.0.2: 279 | version "1.0.2" 280 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 281 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 282 | 283 | supports-preserve-symlinks-flag@^1.0.0: 284 | version "1.0.0" 285 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 286 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 287 | 288 | uuid@^9.0.0: 289 | version "9.0.0" 290 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" 291 | integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== 292 | 293 | vite-plugin-top-level-await@^1.3.0: 294 | version "1.3.0" 295 | resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.3.0.tgz#83c73b5aed33a3819d85432da27f462218cfb3f5" 296 | integrity sha512-owIfsgWudMlQODWJSwp0sQB3AZZu3qsMygeBjZy8CyjEk6OB9AGd8lHqmgwrcEqgvy9N58lYxSBLVk3/4ejEiA== 297 | dependencies: 298 | "@rollup/plugin-virtual" "^3.0.1" 299 | "@swc/core" "^1.3.10" 300 | uuid "^9.0.0" 301 | 302 | vite-plugin-wasm@^3.2.2: 303 | version "3.2.2" 304 | resolved "https://registry.yarnpkg.com/vite-plugin-wasm/-/vite-plugin-wasm-3.2.2.tgz#7a66fef27733a0dea9b2b14f942a6389a2523f7c" 305 | integrity sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA== 306 | 307 | vite@^4.2.0: 308 | version "4.2.2" 309 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6" 310 | integrity sha512-PcNtT5HeDxb3QaSqFYkEum8f5sCVe0R3WK20qxgIvNBZPXU/Obxs/+ubBMeE7nLWeCo2LDzv+8hRYSlcaSehig== 311 | dependencies: 312 | esbuild "^0.17.5" 313 | postcss "^8.4.21" 314 | resolve "^1.22.1" 315 | rollup "^3.18.0" 316 | optionalDependencies: 317 | fsevents "~2.3.2" 318 | --------------------------------------------------------------------------------