├── .gitignore ├── js ├── module.js └── worker.js ├── src ├── utils.rs ├── formulas.rs ├── xml.rs ├── style.rs └── lib.rs ├── deploy.sh ├── index.html ├── Cargo.toml ├── package.json ├── public ├── cdn.html ├── lib.html ├── worker.html └── datasets.js ├── vite.config.js ├── README.md ├── bun.lock ├── Cargo.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /dist 3 | /target 4 | /node_modules 5 | **/*.rs.bk 6 | *.log -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | server=$1 5 | folder=$2 6 | version=$3 7 | 8 | if [ -z "$server" ] || [ -z "$folder" ] || [ -z "$version" ]; then 9 | echo "Usage: $0 " 10 | echo "Example: $0 user@example.com /var/www/apps 1.3.0" 11 | exit 1 12 | fi 13 | 14 | # Extract subversion by dropping the last digit (e.g., 1.3.0 -> 1.3) 15 | subversion=$(echo "$version" | sed 's/\.[0-9]*$//') 16 | 17 | echo "Deploying version: $version" 18 | echo "Creating subversion link: $subversion" 19 | 20 | # Create version directory and upload files 21 | ssh $server "mkdir -p ${folder}/${version}" 22 | scp -r dist/worker.js "$server:${folder}/${version}/worker.js" 23 | scp -r dist/module.js "$server:${folder}/${version}/module.js" 24 | scp -r pkg/json2excel_wasm_bg.wasm "$server:${folder}/${version}/json2excel_wasm_bg.wasm" 25 | 26 | # Remove existing subversion link and create new one 27 | ssh $server "cd \"${folder}\" && rm -f \"./${subversion}\" && ln -s \"${version}\" \"${subversion}\"" 28 | 29 | echo "Deployment complete!" 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple Routing Example 6 | 7 | 8 | 13 |
14 | 15 | 30 | 31 | -------------------------------------------------------------------------------- /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 | serde_json = "1.0" 25 | zip = { version = "0.6.4", default-features = false, features = ["deflate"] } 26 | wee_alloc = { version = "0.4.5", optional = true } 27 | console_error_panic_hook = { version = "0.1.6", optional = true } 28 | gloo-utils = { version = "0.1", features = ["serde"] } 29 | regex = { version = "1.7.3", default-features = false, features = ["std" , "perf"] } 30 | 31 | [dependencies.web-sys] 32 | version = "0.3" 33 | features = [ 34 | "console", 35 | ] 36 | 37 | [dev-dependencies] 38 | wasm-bindgen-test = "0.3.13" 39 | -------------------------------------------------------------------------------- /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.5.2", 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 | "pkg/json2excel_wasm_bg.wasm", 17 | "pkg/json2excel_wasm.js", 18 | "pkg/json2excel_wasm_bg.js", 19 | "pkg/json2excel_wasm.d.ts", 20 | "dist/worker.js", 21 | "dist/module.js" 22 | ], 23 | "module": "dist/module.js", 24 | "scripts": { 25 | "dev": "yarn build && vite --mode worker", 26 | "build:wasm": "cargo build && wasm-pack build --target web", 27 | "build:module": "vite build --mode module", 28 | "build:worker": "vite build --mode worker", 29 | "build": "yarn build:wasm && yarn build:module && yarn build:worker" 30 | }, 31 | "devDependencies": { 32 | "vite": "^4.2.0", 33 | "vite-plugin-wasm": "^3.2.2", 34 | "vite-plugin-top-level-await": "^1.3.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - api 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/lib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - api 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /public/worker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSON2Excel - worker 7 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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.5/worker.js 47 | - https://cdn.dhtmlx.com/libs/json2excel/1.5/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.5/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.5/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 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "json2excel-wasm", 6 | "devDependencies": { 7 | "vite": "^4.2.0", 8 | "vite-plugin-top-level-await": "^1.3.0", 9 | "vite-plugin-wasm": "^3.2.2", 10 | }, 11 | }, 12 | }, 13 | "packages": { 14 | "@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], 15 | 16 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], 17 | 18 | "@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], 19 | 20 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], 21 | 22 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], 23 | 24 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], 25 | 26 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], 27 | 28 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], 29 | 30 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], 31 | 32 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], 33 | 34 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], 35 | 36 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], 37 | 38 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], 39 | 40 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], 41 | 42 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], 43 | 44 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], 45 | 46 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], 47 | 48 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], 49 | 50 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], 51 | 52 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], 53 | 54 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], 55 | 56 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], 57 | 58 | "@rollup/plugin-virtual": ["@rollup/plugin-virtual@3.0.2", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A=="], 59 | 60 | "@swc/core": ["@swc/core@1.13.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", "@swc/core-linux-arm-gnueabihf": "1.13.5", "@swc/core-linux-arm64-gnu": "1.13.5", "@swc/core-linux-arm64-musl": "1.13.5", "@swc/core-linux-x64-gnu": "1.13.5", "@swc/core-linux-x64-musl": "1.13.5", "@swc/core-win32-arm64-msvc": "1.13.5", "@swc/core-win32-ia32-msvc": "1.13.5", "@swc/core-win32-x64-msvc": "1.13.5" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ=="], 61 | 62 | "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ=="], 63 | 64 | "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.13.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng=="], 65 | 66 | "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.13.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ=="], 67 | 68 | "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.13.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw=="], 69 | 70 | "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.13.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ=="], 71 | 72 | "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.13.5", "", { "os": "linux", "cpu": "x64" }, "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA=="], 73 | 74 | "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.13.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q=="], 75 | 76 | "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.13.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw=="], 77 | 78 | "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.13.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw=="], 79 | 80 | "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.13.5", "", { "os": "win32", "cpu": "x64" }, "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q=="], 81 | 82 | "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], 83 | 84 | "@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="], 85 | 86 | "@swc/wasm": ["@swc/wasm@1.13.5", "", {}, "sha512-ZBZcxieydxNwgEU9eFAXGMaDb1Xoh+ZkZcUQ27LNJzc2lPSByoL6CSVqnYiaVo+n9JgqbYyHlMq+i7z0wRNTfA=="], 87 | 88 | "esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], 89 | 90 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 91 | 92 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 93 | 94 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 95 | 96 | "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 97 | 98 | "rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="], 99 | 100 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 101 | 102 | "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], 103 | 104 | "vite": ["vite@4.5.14", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g=="], 105 | 106 | "vite-plugin-top-level-await": ["vite-plugin-top-level-await@1.6.0", "", { "dependencies": { "@rollup/plugin-virtual": "^3.0.2", "@swc/core": "^1.12.14", "@swc/wasm": "^1.12.14", "uuid": "10.0.0" }, "peerDependencies": { "vite": ">=2.8" } }, "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww=="], 107 | 108 | "vite-plugin-wasm": ["vite-plugin-wasm@3.5.0", "", { "peerDependencies": { "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ=="], 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /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 | "serde_json", 127 | "wasm-bindgen", 128 | "wasm-bindgen-test", 129 | "web-sys", 130 | "wee_alloc", 131 | "zip", 132 | ] 133 | 134 | [[package]] 135 | name = "libc" 136 | version = "0.2.169" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 139 | 140 | [[package]] 141 | name = "log" 142 | version = "0.4.25" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 145 | 146 | [[package]] 147 | name = "memchr" 148 | version = "2.7.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 151 | 152 | [[package]] 153 | name = "memory_units" 154 | version = "0.4.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 157 | 158 | [[package]] 159 | name = "minicov" 160 | version = "0.3.7" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" 163 | dependencies = [ 164 | "cc", 165 | "walkdir", 166 | ] 167 | 168 | [[package]] 169 | name = "miniz_oxide" 170 | version = "0.8.3" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" 173 | dependencies = [ 174 | "adler2", 175 | ] 176 | 177 | [[package]] 178 | name = "once_cell" 179 | version = "1.20.2" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 182 | 183 | [[package]] 184 | name = "proc-macro2" 185 | version = "1.0.93" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 188 | dependencies = [ 189 | "unicode-ident", 190 | ] 191 | 192 | [[package]] 193 | name = "quote" 194 | version = "1.0.38" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 197 | dependencies = [ 198 | "proc-macro2", 199 | ] 200 | 201 | [[package]] 202 | name = "regex" 203 | version = "1.11.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 206 | dependencies = [ 207 | "aho-corasick", 208 | "memchr", 209 | "regex-automata", 210 | "regex-syntax", 211 | ] 212 | 213 | [[package]] 214 | name = "regex-automata" 215 | version = "0.4.9" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 218 | dependencies = [ 219 | "aho-corasick", 220 | "memchr", 221 | "regex-syntax", 222 | ] 223 | 224 | [[package]] 225 | name = "regex-syntax" 226 | version = "0.8.5" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 229 | 230 | [[package]] 231 | name = "rustversion" 232 | version = "1.0.19" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 235 | 236 | [[package]] 237 | name = "ryu" 238 | version = "1.0.19" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 241 | 242 | [[package]] 243 | name = "same-file" 244 | version = "1.0.6" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 247 | dependencies = [ 248 | "winapi-util", 249 | ] 250 | 251 | [[package]] 252 | name = "serde" 253 | version = "1.0.217" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 256 | dependencies = [ 257 | "serde_derive", 258 | ] 259 | 260 | [[package]] 261 | name = "serde-wasm-bindgen" 262 | version = "0.5.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" 265 | dependencies = [ 266 | "js-sys", 267 | "serde", 268 | "wasm-bindgen", 269 | ] 270 | 271 | [[package]] 272 | name = "serde_derive" 273 | version = "1.0.217" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 276 | dependencies = [ 277 | "proc-macro2", 278 | "quote", 279 | "syn", 280 | ] 281 | 282 | [[package]] 283 | name = "serde_json" 284 | version = "1.0.138" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 287 | dependencies = [ 288 | "itoa", 289 | "memchr", 290 | "ryu", 291 | "serde", 292 | ] 293 | 294 | [[package]] 295 | name = "shlex" 296 | version = "1.3.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 299 | 300 | [[package]] 301 | name = "syn" 302 | version = "2.0.98" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 305 | dependencies = [ 306 | "proc-macro2", 307 | "quote", 308 | "unicode-ident", 309 | ] 310 | 311 | [[package]] 312 | name = "unicode-ident" 313 | version = "1.0.16" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 316 | 317 | [[package]] 318 | name = "walkdir" 319 | version = "2.5.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 322 | dependencies = [ 323 | "same-file", 324 | "winapi-util", 325 | ] 326 | 327 | [[package]] 328 | name = "wasm-bindgen" 329 | version = "0.2.100" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 332 | dependencies = [ 333 | "cfg-if 1.0.0", 334 | "once_cell", 335 | "rustversion", 336 | "wasm-bindgen-macro", 337 | ] 338 | 339 | [[package]] 340 | name = "wasm-bindgen-backend" 341 | version = "0.2.100" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 344 | dependencies = [ 345 | "bumpalo", 346 | "log", 347 | "proc-macro2", 348 | "quote", 349 | "syn", 350 | "wasm-bindgen-shared", 351 | ] 352 | 353 | [[package]] 354 | name = "wasm-bindgen-futures" 355 | version = "0.4.50" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 358 | dependencies = [ 359 | "cfg-if 1.0.0", 360 | "js-sys", 361 | "once_cell", 362 | "wasm-bindgen", 363 | "web-sys", 364 | ] 365 | 366 | [[package]] 367 | name = "wasm-bindgen-macro" 368 | version = "0.2.100" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 371 | dependencies = [ 372 | "quote", 373 | "wasm-bindgen-macro-support", 374 | ] 375 | 376 | [[package]] 377 | name = "wasm-bindgen-macro-support" 378 | version = "0.2.100" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "syn", 385 | "wasm-bindgen-backend", 386 | "wasm-bindgen-shared", 387 | ] 388 | 389 | [[package]] 390 | name = "wasm-bindgen-shared" 391 | version = "0.2.100" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 394 | dependencies = [ 395 | "unicode-ident", 396 | ] 397 | 398 | [[package]] 399 | name = "wasm-bindgen-test" 400 | version = "0.3.50" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" 403 | dependencies = [ 404 | "js-sys", 405 | "minicov", 406 | "wasm-bindgen", 407 | "wasm-bindgen-futures", 408 | "wasm-bindgen-test-macro", 409 | ] 410 | 411 | [[package]] 412 | name = "wasm-bindgen-test-macro" 413 | version = "0.3.50" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" 416 | dependencies = [ 417 | "proc-macro2", 418 | "quote", 419 | "syn", 420 | ] 421 | 422 | [[package]] 423 | name = "web-sys" 424 | version = "0.3.77" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 427 | dependencies = [ 428 | "js-sys", 429 | "wasm-bindgen", 430 | ] 431 | 432 | [[package]] 433 | name = "wee_alloc" 434 | version = "0.4.5" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 437 | dependencies = [ 438 | "cfg-if 0.1.10", 439 | "libc", 440 | "memory_units", 441 | "winapi", 442 | ] 443 | 444 | [[package]] 445 | name = "winapi" 446 | version = "0.3.9" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 449 | dependencies = [ 450 | "winapi-i686-pc-windows-gnu", 451 | "winapi-x86_64-pc-windows-gnu", 452 | ] 453 | 454 | [[package]] 455 | name = "winapi-i686-pc-windows-gnu" 456 | version = "0.4.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 459 | 460 | [[package]] 461 | name = "winapi-util" 462 | version = "0.1.9" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 465 | dependencies = [ 466 | "windows-sys", 467 | ] 468 | 469 | [[package]] 470 | name = "winapi-x86_64-pc-windows-gnu" 471 | version = "0.4.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 474 | 475 | [[package]] 476 | name = "windows-sys" 477 | version = "0.59.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 480 | dependencies = [ 481 | "windows-targets", 482 | ] 483 | 484 | [[package]] 485 | name = "windows-targets" 486 | version = "0.52.6" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 489 | dependencies = [ 490 | "windows_aarch64_gnullvm", 491 | "windows_aarch64_msvc", 492 | "windows_i686_gnu", 493 | "windows_i686_gnullvm", 494 | "windows_i686_msvc", 495 | "windows_x86_64_gnu", 496 | "windows_x86_64_gnullvm", 497 | "windows_x86_64_msvc", 498 | ] 499 | 500 | [[package]] 501 | name = "windows_aarch64_gnullvm" 502 | version = "0.52.6" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 505 | 506 | [[package]] 507 | name = "windows_aarch64_msvc" 508 | version = "0.52.6" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 511 | 512 | [[package]] 513 | name = "windows_i686_gnu" 514 | version = "0.52.6" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 517 | 518 | [[package]] 519 | name = "windows_i686_gnullvm" 520 | version = "0.52.6" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 523 | 524 | [[package]] 525 | name = "windows_i686_msvc" 526 | version = "0.52.6" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 529 | 530 | [[package]] 531 | name = "windows_x86_64_gnu" 532 | version = "0.52.6" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 535 | 536 | [[package]] 537 | name = "windows_x86_64_gnullvm" 538 | version = "0.52.6" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 541 | 542 | [[package]] 543 | name = "windows_x86_64_msvc" 544 | version = "0.52.6" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 547 | 548 | [[package]] 549 | name = "zip" 550 | version = "0.6.6" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 553 | dependencies = [ 554 | "byteorder", 555 | "crc32fast", 556 | "crossbeam-utils", 557 | "flate2", 558 | ] 559 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/datasets.js: -------------------------------------------------------------------------------- 1 | var example = { 2 | data: [ 3 | { 4 | name: "data", 5 | cols: [ 6 | { 7 | width: 59.5, 8 | }, 9 | { 10 | width: 93.5, 11 | }, 12 | { 13 | width: 160.31, 14 | hidden: true, 15 | }, 16 | { 17 | width: 139.655, 18 | }, 19 | { 20 | width: 150.53499, 21 | }, 22 | { 23 | width: 129.965, 24 | }, 25 | { 26 | width: 139.655, 27 | }, 28 | { 29 | width: 160.31, 30 | }, 31 | { 32 | width: 129.965, 33 | }, 34 | { 35 | width: 129.965, 36 | }, 37 | { 38 | width: 129.965, 39 | }, 40 | { 41 | width: 129.965, 42 | }, 43 | { 44 | width: 129.965, 45 | }, 46 | ], 47 | rows: [ 48 | { 49 | height: 40, 50 | }, 51 | { 52 | height: 40, 53 | }, 54 | { 55 | height: 40, 56 | }, 57 | { 58 | height: 40, 59 | hidden: true, 60 | }, 61 | { 62 | height: 40, 63 | }, 64 | { 65 | height: 40, 66 | }, 67 | { 68 | height: 40, 69 | }, 70 | { 71 | height: 40, 72 | }, 73 | { 74 | height: 40, 75 | }, 76 | { 77 | height: 40, 78 | }, 79 | { 80 | height: 120, 81 | }, 82 | ], 83 | cells: [ 84 | [ 85 | { 86 | v: null, 87 | s: 1, 88 | }, 89 | { 90 | v: "Project", 91 | s: 1, 92 | }, 93 | { 94 | v: "Access", 95 | s: 1, 96 | }, 97 | { 98 | v: "Status", 99 | s: 1, 100 | }, 101 | { 102 | v: "Owner", 103 | s: 1, 104 | }, 105 | { 106 | v: "Balance", 107 | s: 2, 108 | }, 109 | { 110 | v: "Number of hours", 111 | s: 2, 112 | }, 113 | { 114 | v: "Number of renewals", 115 | s: 1, 116 | }, 117 | { 118 | v: "Calendar", 119 | s: 3, 120 | }, 121 | null, 122 | { 123 | v: "Cost", 124 | s: 2, 125 | }, 126 | { 127 | v: "Budget", 128 | s: 2, 129 | }, 130 | { 131 | v: "Project ID", 132 | s: 4, 133 | }, 134 | ], 135 | [ 136 | { 137 | v: "This is a very long text that will demonstrate how the cell handles long content.", 138 | s: 17, 139 | }, 140 | { 141 | v: null, 142 | s: 1, 143 | }, 144 | { 145 | v: null, 146 | s: 1, 147 | }, 148 | { 149 | v: null, 150 | s: 1, 151 | }, 152 | { 153 | v: null, 154 | s: 1, 155 | }, 156 | { 157 | v: null, 158 | s: 2, 159 | }, 160 | { 161 | v: null, 162 | s: 2, 163 | }, 164 | { 165 | v: null, 166 | s: 1, 167 | }, 168 | { 169 | v: "Start date", 170 | s: 3, 171 | }, 172 | { 173 | v: "End date", 174 | s: 3, 175 | }, 176 | { 177 | v: null, 178 | s: 2, 179 | }, 180 | { 181 | v: null, 182 | s: 2, 183 | }, 184 | { 185 | v: null, 186 | s: 1, 187 | }, 188 | ], 189 | [ 190 | { 191 | v: "true", 192 | s: 5, 193 | }, 194 | { 195 | v: "Real Estate", 196 | s: 6, 197 | }, 198 | { 199 | v: "Gary Ortiz, Albert Williamson, Russell Robinson", 200 | s: 6, 201 | }, 202 | { 203 | v: "Done", 204 | s: 7, 205 | hyperlink: 206 | "https://git.webix.io/iizobov/salesforce/src/branch/prod", 207 | }, 208 | { 209 | v: "Louise Fisher", 210 | s: 8, 211 | }, 212 | { 213 | v: "8180.0", 214 | s: 9, 215 | }, 216 | { 217 | v: "92.0", 218 | s: 10, 219 | }, 220 | { 221 | v: "1-2 times", 222 | s: 6, 223 | }, 224 | { 225 | v: "43134.0", 226 | s: 11, 227 | }, 228 | { 229 | v: "43256.0", 230 | s: 11, 231 | }, 232 | { 233 | v: "3588.0", 234 | s: 9, 235 | }, 236 | { 237 | v: "11768.0", 238 | s: 9, 239 | }, 240 | { 241 | v: "ISS-996.9", 242 | s: 6, 243 | }, 244 | ], 245 | [ 246 | { 247 | v: "false", 248 | s: 5, 249 | }, 250 | { 251 | v: null, 252 | s: 6, 253 | }, 254 | { 255 | v: "Mildred Fuller, Gary Ortiz", 256 | s: 6, 257 | }, 258 | { 259 | v: "Done", 260 | s: 6, 261 | }, 262 | { 263 | v: "Fred Duncan", 264 | s: 8, 265 | }, 266 | { 267 | v: "-6389.0", 268 | s: 9, 269 | }, 270 | { 271 | v: "484.0", 272 | s: 12, 273 | }, 274 | { 275 | v: "1 time", 276 | s: 6, 277 | }, 278 | { 279 | v: "43106.0", 280 | s: 11, 281 | }, 282 | { 283 | v: "43109.0", 284 | s: 11, 285 | }, 286 | { 287 | v: "21296.0", 288 | s: 9, 289 | }, 290 | { 291 | v: "14907.0", 292 | s: 9, 293 | }, 294 | { 295 | v: "ISS-111.0", 296 | s: 6, 297 | }, 298 | ], 299 | [ 300 | { 301 | v: "false", 302 | s: 5, 303 | }, 304 | { 305 | v: null, 306 | s: 6, 307 | }, 308 | { 309 | v: "Albert Williamson, Russell Robinson, Phyllis Webb", 310 | s: 6, 311 | }, 312 | { 313 | v: "Louise Fisher", 314 | s: 13, 315 | }, 316 | { 317 | v: "Michael Rice", 318 | s: 8, 319 | }, 320 | { 321 | v: "56076.0", 322 | s: 9, 323 | }, 324 | { 325 | v: "345.0", 326 | s: 10, 327 | }, 328 | { 329 | v: "1-2 times", 330 | s: 6, 331 | }, 332 | { 333 | v: "43108.0", 334 | s: 11, 335 | }, 336 | { 337 | v: "43262.0", 338 | s: 11, 339 | }, 340 | { 341 | v: "14835.0", 342 | s: 9, 343 | }, 344 | { 345 | v: "70911.0", 346 | s: 9, 347 | }, 348 | { 349 | v: "ISS-678.9", 350 | s: 6, 351 | }, 352 | ], 353 | [ 354 | { 355 | v: "true", 356 | s: 5, 357 | }, 358 | { 359 | v: "HR System", 360 | s: 6, 361 | }, 362 | { 363 | v: "Russell Robinson, Phyllis Webb", 364 | s: 6, 365 | }, 366 | { 367 | v: "Done", 368 | s: 6, 369 | }, 370 | { 371 | v: "Daniel Peterson", 372 | s: 8, 373 | }, 374 | { 375 | v: "2876.0", 376 | s: 9, 377 | }, 378 | { 379 | v: "340.0", 380 | s: 10, 381 | }, 382 | { 383 | v: "1 time", 384 | s: 6, 385 | }, 386 | { 387 | v: "43163.0", 388 | s: 11, 389 | }, 390 | { 391 | v: "43138.0", 392 | s: 11, 393 | }, 394 | { 395 | v: "15980.0", 396 | s: 9, 397 | }, 398 | { 399 | v: "18856.0", 400 | s: 9, 401 | }, 402 | { 403 | v: "ISS-134.2", 404 | s: 6, 405 | }, 406 | ], 407 | [ 408 | { 409 | v: "true", 410 | s: 5, 411 | }, 412 | { 413 | v: null, 414 | s: 6, 415 | }, 416 | { 417 | v: "Gary Ortiz, Albert Williamson, Russell Robinson, Phyllis Webb", 418 | s: 6, 419 | }, 420 | { 421 | v: "Done", 422 | s: 6, 423 | }, 424 | { 425 | v: "Andrew Stewart", 426 | s: 8, 427 | }, 428 | { 429 | v: "3016.0", 430 | s: 9, 431 | }, 432 | { 433 | v: "57.0", 434 | s: 10, 435 | }, 436 | { 437 | v: "1-2 times", 438 | s: 6, 439 | }, 440 | { 441 | v: "43106.0", 442 | s: 11, 443 | }, 444 | { 445 | v: "43140.0", 446 | s: 11, 447 | }, 448 | { 449 | v: "2052.0", 450 | s: 9, 451 | }, 452 | { 453 | v: "5068.0", 454 | s: 9, 455 | }, 456 | { 457 | v: "ISS-433.2", 458 | s: 6, 459 | }, 460 | ], 461 | [ 462 | { 463 | v: "false", 464 | s: 5, 465 | }, 466 | { 467 | v: null, 468 | s: 6, 469 | }, 470 | { 471 | v: "Mildred Fuller, Phyllis Webb", 472 | s: 6, 473 | }, 474 | { 475 | v: "Done", 476 | s: 6, 477 | }, 478 | { 479 | v: "Martin Thompson", 480 | s: 8, 481 | }, 482 | { 483 | v: "8311.0", 484 | s: 9, 485 | }, 486 | { 487 | v: "211.0", 488 | s: 10, 489 | }, 490 | { 491 | v: "more than 5 times", 492 | s: 6, 493 | }, 494 | { 495 | v: "43253.0", 496 | s: 11, 497 | }, 498 | { 499 | v: "43106.0", 500 | s: 11, 501 | }, 502 | { 503 | v: "8229.0", 504 | s: 9, 505 | }, 506 | { 507 | v: "16540.0", 508 | s: 9, 509 | }, 510 | { 511 | v: "ISS-356.2", 512 | s: 6, 513 | }, 514 | ], 515 | [ 516 | { 517 | v: "true", 518 | s: 5, 519 | }, 520 | { 521 | v: "Ticket System", 522 | s: 6, 523 | }, 524 | { 525 | v: "Mildred Fuller, Albert Williamson", 526 | s: 6, 527 | }, 528 | { 529 | v: "In Progress", 530 | s: 6, 531 | }, 532 | { 533 | v: "Martin Thompson", 534 | s: 8, 535 | }, 536 | { 537 | v: "-22.0", 538 | s: 9, 539 | }, 540 | { 541 | v: "3.0", 542 | s: 14, 543 | }, 544 | { 545 | v: "1 time", 546 | s: 6, 547 | }, 548 | { 549 | v: "43621.0", 550 | s: 11, 551 | }, 552 | { 553 | v: "43531.0", 554 | s: 11, 555 | }, 556 | { 557 | v: "144.0", 558 | s: 9, 559 | }, 560 | { 561 | v: "122.0", 562 | s: 9, 563 | }, 564 | { 565 | v: "ISS-342.1", 566 | s: 6, 567 | }, 568 | ], 569 | [ 570 | { 571 | v: "true", 572 | s: 5, 573 | }, 574 | { 575 | v: null, 576 | s: 6, 577 | }, 578 | { 579 | v: "Gary Ortiz, Phyllis Webb", 580 | s: 6, 581 | }, 582 | { 583 | v: "In Progress", 584 | s: 6, 585 | }, 586 | { 587 | v: "Mark Harper", 588 | s: 8, 589 | }, 590 | { 591 | v: "9019.0", 592 | s: 9, 593 | }, 594 | { 595 | v: "76.0", 596 | s: 10, 597 | }, 598 | { 599 | v: "more than 5 times", 600 | s: 6, 601 | }, 602 | { 603 | v: "43500.0", 604 | s: 11, 605 | }, 606 | { 607 | v: "43532.0", 608 | s: 11, 609 | }, 610 | { 611 | v: "3496.0", 612 | s: 9, 613 | }, 614 | { 615 | v: "12515.0", 616 | s: 9, 617 | }, 618 | { 619 | v: "ISS-256.7", 620 | s: 6, 621 | }, 622 | ], 623 | [ 624 | { 625 | v: null, 626 | s: 15, 627 | }, 628 | { 629 | v: "Total", 630 | s: 15, 631 | }, 632 | { 633 | v: null, 634 | s: 15, 635 | }, 636 | { 637 | v: null, 638 | s: 15, 639 | }, 640 | { 641 | v: "s", 642 | s: 16, 643 | }, 644 | { 645 | v: "56076", 646 | s: 15, 647 | }, 648 | { 649 | v: "Avg:201.0\n Med:151.5\n Max:484.0\n Min:3.0\n Sum:1608.0", 650 | s: 15, 651 | }, 652 | { 653 | v: null, 654 | s: 15, 655 | }, 656 | { 657 | v: null, 658 | s: 15, 659 | }, 660 | { 661 | v: null, 662 | s: 15, 663 | }, 664 | { 665 | v: "69620", 666 | s: 15, 667 | }, 668 | { 669 | v: "150687", 670 | s: 15, 671 | }, 672 | { 673 | v: null, 674 | s: 15, 675 | }, 676 | ], 677 | ], 678 | merged: [ 679 | { 680 | from: { 681 | column: 8, 682 | row: 0, 683 | }, 684 | to: { 685 | column: 9, 686 | row: 0, 687 | }, 688 | }, 689 | ], 690 | validations: [ 691 | { 692 | range: "H3 D6:D9", 693 | source: { 694 | type: "RangeReference", 695 | value: "data!$E$3:$E$10", 696 | }, 697 | }, 698 | { 699 | range: "E11", 700 | source: { 701 | type: "RangeReference", 702 | value: 'NOT(ISERROR(SEARCH(("Some"),(E11))))', 703 | }, 704 | }, 705 | { 706 | range: "B2", 707 | source: { 708 | type: "List", 709 | value: ["Apple", "Mango", "Avocado"], 710 | }, 711 | }, 712 | ], 713 | frozen_cols: 2, 714 | frozen_rows: 1, 715 | }, 716 | ], 717 | styles: [ 718 | { 719 | format: "General", 720 | fontSize: "13.333333px", 721 | verticalAlign: "bottom", 722 | fontFamily: "Calibri", 723 | color: "rgba(0,0,0,1)", 724 | }, 725 | { 726 | color: "rgba(0,0,0,1)", 727 | verticalAlign: "center", 728 | fontWeight: "bold", 729 | format: "General", 730 | background: "rgba(247,247,247,1)", 731 | fontFamily: "Calibri", 732 | }, 733 | { 734 | color: "rgba(0,0,0,1)", 735 | background: "rgba(247,247,247,1)", 736 | fontWeight: "bold", 737 | fontFamily: "Calibri", 738 | align: "right", 739 | format: "General", 740 | verticalAlign: "center", 741 | }, 742 | { 743 | background: "rgba(247,247,247,1)", 744 | color: "rgba(0,0,0,1)", 745 | align: "center", 746 | fontFamily: "Calibri", 747 | fontWeight: "bold", 748 | verticalAlign: "center", 749 | format: "dd/mm/yy", 750 | }, 751 | { 752 | fontWeight: "bold", 753 | format: "@", 754 | verticalAlign: "center", 755 | fontFamily: "Calibri", 756 | background: "rgba(247,247,247,1)", 757 | color: "rgba(0,0,0,1)", 758 | align: "center", 759 | }, 760 | { 761 | color: "rgba(0,0,0,1)", 762 | fontSize: "13.333333px", 763 | format: "General", 764 | fontFamily: "Calibri", 765 | verticalAlign: "center", 766 | }, 767 | { 768 | color: "rgba(0,0,0,1)", 769 | fontFamily: "Calibri", 770 | fontSize: "13.333333px", 771 | format: "@", 772 | verticalAlign: "center", 773 | }, 774 | { 775 | color: "rgba(0,0,255,1)", 776 | format: "@", 777 | textDecoration: "underline", 778 | verticalAlign: "center", 779 | fontSize: "13.333333px", 780 | }, 781 | { 782 | format: "@", 783 | fontSize: "13.333333px", 784 | fontFamily: "Calibri", 785 | verticalAlign: "center", 786 | background: "rgba(173,216,230,1)", 787 | color: "rgba(0,0,0,1)", 788 | }, 789 | { 790 | fontFamily: "Calibri", 791 | align: "right", 792 | format: "General", 793 | verticalAlign: "center", 794 | color: "rgba(0,0,0,1)", 795 | fontSize: "13.333333px", 796 | }, 797 | { 798 | fontFamily: "Calibri", 799 | fontSize: "13.333333px", 800 | format: "#.##", 801 | align: "right", 802 | verticalAlign: "center", 803 | color: "rgba(0,0,0,1)", 804 | }, 805 | { 806 | fontFamily: "Calibri", 807 | format: "dd/mm/yy", 808 | verticalAlign: "center", 809 | fontSize: "13.333333px", 810 | align: "center", 811 | color: "rgba(0,0,0,1)", 812 | }, 813 | { 814 | color: "rgba(255,255,255,1)", 815 | format: "#.##", 816 | fontFamily: "Calibri", 817 | verticalAlign: "center", 818 | background: "rgba(244,67,54,1)", 819 | align: "right", 820 | fontSize: "13.333333px", 821 | }, 822 | { 823 | fontFamily: "Calibri", 824 | format: "@", 825 | color: "rgba(0,0,0,1)", 826 | fontSize: "13.333333px", 827 | verticalAlign: "center", 828 | }, 829 | { 830 | format: "#.##", 831 | fontSize: "13.333333px", 832 | fontFamily: "Calibri", 833 | color: "rgba(255,255,255,1)", 834 | verticalAlign: "center", 835 | align: "right", 836 | background: "rgba(76,175,80,1)", 837 | }, 838 | { 839 | fontFamily: "Calibri", 840 | background: "rgba(247,247,247,1)", 841 | fontWeight: "bold", 842 | fontSize: "13.333333px", 843 | format: "General", 844 | verticalAlign: "center", 845 | color: "rgba(0,0,0,1)", 846 | }, 847 | { 848 | verticalAlign: "center", 849 | color: "rgba(0,0,0,1)", 850 | fontWeight: "bold", 851 | fontFamily: "Calibri", 852 | background: "rgba(247,247,247,1)", 853 | fontSize: "13.333333px", 854 | format: "General", 855 | }, 856 | { 857 | verticalAlign: "center", 858 | color: "rgba(0,0,0,1)", 859 | fontWeight: "bold", 860 | fontFamily: "Calibri", 861 | background: "rgba(247,247,247,1)", 862 | fontSize: "13.333333px", 863 | format: "General", 864 | wrapText: true, 865 | }, 866 | ], 867 | }; 868 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde_json::Value; 3 | 4 | use crate::xml::Element; 5 | 6 | #[derive(PartialEq)] 7 | pub struct Font { 8 | pub name: Option, 9 | pub size: Option, 10 | pub color: Option, 11 | pub bold: bool, 12 | pub italic: bool, 13 | pub underline: bool, 14 | pub strike: bool, 15 | } 16 | 17 | #[derive(PartialEq)] 18 | pub struct Fill { 19 | pub pattern_type: String, 20 | pub color: Option, 21 | } 22 | 23 | #[derive(PartialEq)] 24 | pub struct Border { 25 | pub top: Option, 26 | pub right: Option, 27 | pub bottom: Option, 28 | pub left: Option, 29 | } 30 | 31 | pub struct StyleProps { 32 | pub font: Option, 33 | pub fill: Option, 34 | pub border: Option, 35 | pub align_h: Option, 36 | pub align_v: Option, 37 | pub wrap_text: bool, 38 | } 39 | 40 | #[derive(PartialEq, Debug)] 41 | pub enum BorderStyle { 42 | Medium, 43 | Dotted, 44 | Thick, 45 | Thin, 46 | Dashed, 47 | Double, 48 | } 49 | 50 | #[derive(PartialEq, Debug)] 51 | pub enum BorderPosition { 52 | Top, 53 | Right, 54 | Bottom, 55 | Left, 56 | } 57 | 58 | #[derive(PartialEq)] 59 | pub struct BorderProps { 60 | pub position: BorderPosition, 61 | pub size: f32, 62 | pub style: BorderStyle, 63 | pub color: String, 64 | } 65 | 66 | pub struct StyleTable { 67 | pub fonts: Vec, 68 | pub fills: Vec, 69 | pub borders: Vec, 70 | pub xfs: Vec, 71 | pub custom_formats: HashMap, 72 | next_custom_format: u32, 73 | } 74 | 75 | pub struct XFSProps { 76 | pub font_id: Option, 77 | pub border_id: Option, 78 | pub fill_id: Option, 79 | pub format_id: Option, 80 | pub align_h: Option, 81 | pub align_v: Option, 82 | pub wrap_text: bool, 83 | } 84 | 85 | impl StyleTable { 86 | pub fn new(css: Option>>) -> StyleTable { 87 | let mut table = StyleTable { 88 | fonts: vec![Font::new()], 89 | fills: vec![Fill::new(None, "none"), Fill::new(None, "gray125")], 90 | borders: vec![Border::new()], 91 | xfs: vec![XFSProps::new()], 92 | custom_formats: HashMap::new(), 93 | next_custom_format: 165, 94 | }; 95 | table.custom_formats.insert(String::from("General"), 164); 96 | 97 | css.map(|map| { 98 | for style in map { 99 | table.add(style); 100 | } 101 | }); 102 | 103 | table 104 | } 105 | pub fn add(&mut self, style: HashMap) { 106 | let mut xsf_props: XFSProps = XFSProps::new(); 107 | let st = style_to_props(&style); 108 | 109 | xsf_props.font_id = st 110 | .font 111 | .map(|font| match self.fonts.iter().position(|f| f == &font) { 112 | Some(v) => v, 113 | None => { 114 | self.fonts.push(font); 115 | self.fonts.len() - 1 116 | } 117 | }); 118 | xsf_props.fill_id = st 119 | .fill 120 | .map(|fill| match self.fills.iter().position(|f| f == &fill) { 121 | Some(v) => v, 122 | None => { 123 | self.fills.push(fill); 124 | self.fills.len() - 1 125 | } 126 | }); 127 | xsf_props.border_id = 128 | st.border.map( 129 | |border| match self.borders.iter().position(|b| b == &border) { 130 | Some(v) => v, 131 | None => { 132 | self.borders.push(border); 133 | self.borders.len() - 1 134 | } 135 | }, 136 | ); 137 | xsf_props.align_h = st.align_h; 138 | xsf_props.align_v = st.align_v; 139 | xsf_props.wrap_text = st.wrap_text; 140 | xsf_props.format_id = style.get("format").and_then(|format_name| { 141 | match format_name { 142 | Value::String(s) => { 143 | let i = match get_format_code(s) { 144 | Some(format) => format, 145 | None => { 146 | if self.custom_formats.contains_key(s) { 147 | self.custom_formats.get(s).unwrap().to_owned() 148 | } else { 149 | let index = self.next_custom_format; 150 | self.custom_formats.insert(s.to_owned(), index); 151 | self.next_custom_format += 1; 152 | index 153 | } 154 | } 155 | }; 156 | Some(i as usize) 157 | } 158 | _ => None, 159 | } 160 | }); 161 | 162 | self.xfs.push(xsf_props); 163 | } 164 | } 165 | 166 | impl Fill { 167 | pub fn new(color: Option, pattern_type: &str) -> Fill { 168 | Fill { 169 | pattern_type: pattern_type.to_owned(), 170 | color: color, 171 | } 172 | } 173 | } 174 | 175 | impl Font { 176 | pub fn new() -> Font { 177 | Font { 178 | name: None, 179 | size: None, 180 | color: None, 181 | bold: false, 182 | italic: false, 183 | underline: false, 184 | strike: false, 185 | } 186 | } 187 | } 188 | 189 | impl Border { 190 | pub fn new() -> Border { 191 | Border { 192 | top: None, 193 | right: None, 194 | bottom: None, 195 | left: None, 196 | } 197 | } 198 | } 199 | 200 | impl BorderProps { 201 | pub fn to_xml_el(&self) -> Element { 202 | let el_name = match self.position { 203 | BorderPosition::Top => "top", 204 | BorderPosition::Right => "right", 205 | BorderPosition::Bottom => "bottom", 206 | BorderPosition::Left => "left", 207 | }; 208 | 209 | let mut el = Element::new(el_name); 210 | 211 | let attr_style = match self.style { 212 | BorderStyle::Dotted => "dotted", 213 | BorderStyle::Thick => "thick", 214 | BorderStyle::Double => "double", 215 | BorderStyle::Dashed => "hair", 216 | BorderStyle::Medium => "medium", 217 | BorderStyle::Thin => "thin", 218 | }; 219 | 220 | el.add_attr("style", attr_style); 221 | 222 | if !self.color.is_empty() { 223 | let mut el_color = Element::new("color"); 224 | el_color.add_attr("rgb", self.color.to_owned()); 225 | el.add_children(vec![el_color]); 226 | } 227 | 228 | el 229 | } 230 | } 231 | 232 | impl StyleProps { 233 | pub fn new() -> StyleProps { 234 | StyleProps { 235 | align_h: None, 236 | fill: None, 237 | font: None, 238 | border: None, 239 | align_v: None, 240 | wrap_text: false, 241 | } 242 | } 243 | } 244 | 245 | impl XFSProps { 246 | pub fn new() -> XFSProps { 247 | XFSProps { 248 | font_id: None, 249 | fill_id: None, 250 | border_id: None, 251 | format_id: None, 252 | align_h: None, 253 | align_v: None, 254 | wrap_text: false, 255 | } 256 | } 257 | } 258 | 259 | fn style_to_props(styles: &HashMap) -> StyleProps { 260 | let mut font: Font = Font::new(); 261 | let mut border: Border = Border::new(); 262 | let mut st = StyleProps::new(); 263 | 264 | for (key, value) in styles { 265 | match key.as_ref() { 266 | "background" => match value { 267 | Value::String(s) => match color_to_argb(s) { 268 | Some(v) => st.fill = Some(Fill::new(Some(v), "solid")), 269 | None => (), 270 | }, 271 | _ => (), 272 | }, 273 | "color" => match value { 274 | Value::String(s) => font.color = color_to_argb(s), 275 | _ => (), 276 | }, 277 | "fontWeight" => font.bold = value.as_str().map(|s| s == "bold").unwrap_or(false), 278 | "fontStyle" => font.italic = value.as_str().map(|s| s == "italic").unwrap_or(false), 279 | "fontFamily" => font.name = value.as_str().map(|s| s.to_string()), 280 | "textDecoration" => match value { 281 | Value::String(s) => { 282 | font.underline = s.contains("underline"); 283 | font.strike = s.contains("line-through"); 284 | } 285 | _ => (), 286 | }, 287 | "fontSize" => font.size = value.as_str().and_then(|s| px_to_pt(s)), 288 | "align" => st.align_h = value.as_str().map(|s| s.to_string()), 289 | "verticalAlign" => st.align_v = value.as_str().map(|s| s.to_string()), 290 | "borderTop" => border.top = value.as_str().and_then(|s| str_to_border(s, BorderPosition::Top)), 291 | "borderRight" => border.right = value.as_str().and_then(|s| str_to_border(s, BorderPosition::Right)), 292 | "borderBottom" => border.bottom = value.as_str().and_then(|s| str_to_border(s, BorderPosition::Bottom)), 293 | "borderLeft" => border.left = value.as_str().and_then(|s| str_to_border(s, BorderPosition::Left)), 294 | "wrapText" => { 295 | // accept boolean, string or number values 296 | match value { 297 | Value::Bool(b) => st.wrap_text = *b, 298 | Value::String(s) => st.wrap_text = s == "true" || s == "1", 299 | Value::Number(n) => { 300 | if let Some(i) = n.as_i64() { 301 | st.wrap_text = i != 0; 302 | } else if let Some(f) = n.as_f64() { 303 | st.wrap_text = f != 0.0; 304 | } 305 | } 306 | _ => (), 307 | } 308 | } 309 | _ => (), 310 | } 311 | } 312 | 313 | st.font = Some(font); 314 | st.border = Some(border); 315 | 316 | st 317 | } 318 | 319 | fn color_to_argb(color: &str) -> Option { 320 | let len = color.len(); 321 | let mut argb_color = String::new(); 322 | if len == 4 && &color[0..1] == "#" { 323 | let hex3 = &color[1..4]; 324 | let r = &hex3[0..1]; 325 | let g = &hex3[1..2]; 326 | let b = &hex3[2..3]; 327 | let argb = format!("FF{}{}{}{}{}{}", r, r, g, g, b, b); 328 | Some(argb) 329 | } else if len == 7 && &color[0..1] == "#" { 330 | argb_color.push_str("FF"); 331 | argb_color.push_str(&color[1..]); 332 | Some(argb_color) 333 | } else if len > 11 && &color[0..5] == "rgba(" && &color[len - 1..] == ")" { 334 | let colors_part = &color[5..len - 1]; 335 | let colors = colors_part 336 | .split(",") 337 | .map(|s| s.trim()) 338 | .collect::>(); 339 | if colors.len() < 4 { 340 | return None; 341 | } 342 | let r = str_to_hex(colors[0]); 343 | let g = str_to_hex(colors[1]); 344 | let b = str_to_hex(colors[2]); 345 | let a = str_alpha_to_hex(colors[3]); 346 | if r.is_none() || g.is_none() || b.is_none() || a.is_none() { 347 | return None; 348 | } 349 | argb_color.push_str(&a.unwrap()); 350 | argb_color.push_str(&r.unwrap()); 351 | argb_color.push_str(&g.unwrap()); 352 | argb_color.push_str(&b.unwrap()); 353 | Some(argb_color) 354 | } else if len > 10 && &color[0..4] == "rgb(" && &color[len - 1..] == ")" { 355 | let colors_part = &color[4..len - 1]; 356 | let colors = colors_part 357 | .split(",") 358 | .map(|s| s.trim()) 359 | .collect::>(); 360 | if colors.len() < 3 { 361 | return None; 362 | } 363 | let r = str_to_hex(colors[0]); 364 | let g = str_to_hex(colors[1]); 365 | let b = str_to_hex(colors[2]); 366 | if r.is_none() || g.is_none() || b.is_none() { 367 | return None; 368 | } 369 | argb_color.push_str("FF"); 370 | argb_color.push_str(&r.unwrap()); 371 | argb_color.push_str(&g.unwrap()); 372 | argb_color.push_str(&b.unwrap()); 373 | Some(argb_color) 374 | } else { 375 | None 376 | } 377 | } 378 | 379 | fn str_to_hex(s: &str) -> Option { 380 | match s.parse::() { 381 | Ok(v) => { 382 | let res = format!("{:X}", v); 383 | match res.len() { 384 | 1 => Some(String::from("0") + &res), 385 | 2 => Some(res), 386 | _ => None, 387 | } 388 | } 389 | Err(_) => None, 390 | } 391 | } 392 | 393 | fn str_alpha_to_hex(s: &str) -> Option { 394 | match s.parse::() { 395 | Ok(v) => { 396 | let res = format!("{:X}", (v * 255f32) as u32); 397 | match res.len() { 398 | 1 => Some(String::from("0") + &res), 399 | 2 => Some(res), 400 | _ => None, 401 | } 402 | } 403 | Err(_) => None, 404 | } 405 | } 406 | 407 | fn px_to_pt(size: &str) -> Option { 408 | let len = size.len(); 409 | if &size[len - 2..].to_owned() != "px" { 410 | None 411 | } else { 412 | match size[0..len - 2].to_owned().parse::() { 413 | Ok(v) => Some((v * 0.75).to_string()), 414 | Err(_) => None, 415 | } 416 | } 417 | } 418 | 419 | fn get_format_code(format: &str) -> Option { 420 | match format { 421 | "" | "General" => Some(0), 422 | "0" => Some(1), 423 | "0.00" => Some(2), 424 | "#,##0" => Some(3), 425 | "#,##0.00" => Some(4), 426 | "0%" => Some(9), 427 | "0.00%" => Some(10), 428 | "0.00E+00" => Some(11), 429 | "# ?/?" => Some(12), 430 | "# ??/??" => Some(13), 431 | "mm-dd-yy" => Some(14), 432 | "d-mmm-yy" => Some(15), 433 | "d-mmm" => Some(16), 434 | "mmm-yy" => Some(17), 435 | "h:mm AM/PM" => Some(18), 436 | "h:mm:ss AM/PM" => Some(19), 437 | "h:mm" => Some(20), 438 | "h:mm:ss" => Some(21), 439 | "m/d/yy h:mm" => Some(22), 440 | "#,##0 ;(#,##0)" => Some(37), 441 | "#,##0 ;[Red](#,##0)" => Some(38), 442 | "#,##0.00;[Red](#,##0.00)" => Some(40), 443 | "mm:ss" => Some(45), 444 | "[h]:mm:ss" => Some(46), 445 | "mmss.0" => Some(47), 446 | "##0.0E+0" => Some(48), 447 | "@" => Some(49), 448 | _ => None, 449 | } 450 | } 451 | 452 | fn str_to_border(v: &str, pos: BorderPosition) -> Option { 453 | let parts = v.split(" "); 454 | let vals: Vec<&str> = parts.collect(); 455 | 456 | if vals.len() != 3 { 457 | return None; 458 | } 459 | 460 | let size = match vals[0].to_string().trim_end_matches("px").parse::() { 461 | Ok(s) => s, 462 | Err(_) => return None, 463 | }; 464 | 465 | let style = match vals[1] { 466 | "thin" => BorderStyle::Thin, 467 | "dotted" => BorderStyle::Dotted, 468 | "double" => BorderStyle::Double, 469 | "dashed" => BorderStyle::Dashed, 470 | "solid" => { 471 | let mut st = BorderStyle::Thin; 472 | 473 | if size == 0.5 { 474 | st = BorderStyle::Thin 475 | } else if size == 1.0 { 476 | st = BorderStyle::Medium 477 | } else if size == 2.0 { 478 | st = BorderStyle::Thick 479 | } 480 | 481 | st 482 | } 483 | _ => return None, 484 | }; 485 | 486 | let color: String = match color_to_argb(vals[2]) { 487 | Some(s) => s, 488 | None => return None, 489 | }; 490 | 491 | Some(BorderProps { 492 | position: pos, 493 | size: size, 494 | style: style, 495 | color: color, 496 | }) 497 | } 498 | 499 | #[test] 500 | fn style_to_props_test() { 501 | let mut styles: HashMap = HashMap::new(); 502 | styles.insert(String::from("background"), Value::String(String::from("#FF0000"))); 503 | styles.insert(String::from("color"), Value::String(String::from("#FFFF00"))); 504 | styles.insert(String::from("fontWeight"), Value::String(String::from("bold"))); 505 | styles.insert(String::from("fontStyle"), Value::String(String::from("italic"))); 506 | styles.insert(String::from("fontSize"), Value::String(String::from("24px"))); 507 | styles.insert(String::from("fontFamily"), Value::String(String::from("Calibri"))); 508 | styles.insert(String::from("textDecoration"), Value::String(String::from("underline"))); 509 | styles.insert(String::from("align"), Value::String(String::from("left"))); 510 | styles.insert(String::from("verticalAlign"), Value::String(String::from("bottom"))); 511 | styles.insert(String::from("borderTop"), Value::String(String::from("1px solid #9AFF02"))); 512 | styles.insert( 513 | String::from("borderRight"), 514 | Value::String(String::from("1px solid #000000")), 515 | ); 516 | 517 | let st = style_to_props(&styles); 518 | 519 | let font = st.font.unwrap(); 520 | assert_eq!(font.size, Some(String::from("18"))); 521 | assert_eq!(font.color, Some(String::from("FFFFFF00"))); 522 | assert_eq!(font.bold, true); 523 | assert_eq!(font.name, Some(String::from("Calibri"))); 524 | assert_eq!(font.italic, true); 525 | assert_eq!(font.underline, true); 526 | assert_eq!(st.fill.unwrap().color, Some(String::from("FFFF0000"))); 527 | assert_eq!(st.align_h, Some(String::from("left"))); 528 | assert_eq!(st.align_v, Some(String::from("bottom"))); 529 | 530 | let border = st.border.unwrap(); 531 | assert_eq!(border.top.unwrap().color, String::from("FF9AFF02")); 532 | assert_eq!(border.right.unwrap().color, String::from("FF000000")); 533 | } 534 | 535 | #[test] 536 | fn str_to_hex_test() { 537 | assert_eq!(str_to_hex("255"), Some(String::from("FF"))); 538 | assert_eq!(str_alpha_to_hex("0.5"), Some(String::from("7F"))); 539 | } 540 | 541 | #[test] 542 | fn color_to_argb_test() { 543 | assert_eq!( 544 | color_to_argb("rgba(255, 255, 255, 1)"), 545 | Some(String::from("FFFFFFFF")) 546 | ); 547 | assert_eq!( 548 | color_to_argb("rgb(254,254,254)"), 549 | Some(String::from("FFFEFEFE")) 550 | ); 551 | assert_eq!(color_to_argb("#FF6347"), Some(String::from("FFFF6347"))); 552 | assert_eq!(color_to_argb("#A4B"), Some(String::from("FFAA44BB"))); 553 | } 554 | 555 | #[test] 556 | fn str_to_border_test() { 557 | let mut maybe_border = str_to_border("1px solid #9900CC", BorderPosition::Top); 558 | let mut b = maybe_border.unwrap(); 559 | assert_eq!(b.color, "FF9900CC"); 560 | assert_eq!(b.position, BorderPosition::Top); 561 | assert_eq!(b.size, 1.0); 562 | assert_eq!(b.style, BorderStyle::Medium); 563 | 564 | maybe_border = str_to_border("0.5px solid #B3FFB3", BorderPosition::Right); 565 | b = maybe_border.unwrap(); 566 | assert_eq!(b.color, "FFB3FFB3"); 567 | assert_eq!(b.position, BorderPosition::Right); 568 | assert_eq!(b.size, 0.5); 569 | assert_eq!(b.style, BorderStyle::Thin); 570 | 571 | maybe_border = str_to_border("2px solid #BCD", BorderPosition::Bottom); 572 | b = maybe_border.unwrap(); 573 | assert_eq!(b.color, "FFBBCCDD"); 574 | assert_eq!(b.position, BorderPosition::Bottom); 575 | assert_eq!(b.size, 2.0); 576 | assert_eq!(b.style, BorderStyle::Thick); 577 | 578 | maybe_border = str_to_border("2px dashed #BCD", BorderPosition::Left); 579 | b = maybe_border.unwrap(); 580 | assert_eq!(b.style, BorderStyle::Dashed); 581 | 582 | maybe_border = str_to_border("2px dotted #BCD", BorderPosition::Left); 583 | b = maybe_border.unwrap(); 584 | assert_eq!(b.style, BorderStyle::Dotted); 585 | 586 | maybe_border = str_to_border("2px double #BCD", BorderPosition::Left); 587 | b = maybe_border.unwrap(); 588 | assert_eq!(b.style, BorderStyle::Double); 589 | } 590 | -------------------------------------------------------------------------------- /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 | use serde_json::Value; 17 | 18 | type Dict = HashMap; 19 | 20 | pub mod xml; 21 | use crate::xml::Element; 22 | pub mod style; 23 | use crate::style::StyleTable; 24 | pub mod utils; 25 | pub mod formulas; 26 | 27 | const WIDTH_COEF: f32 = 8.5; 28 | const HEIGHT_COEF: f32 = 0.75; 29 | 30 | const ROOT_RELS: &'static [u8] = br#" 31 | "#; 32 | 33 | 34 | #[derive(Deserialize)] 35 | pub struct ColumnData { 36 | pub width: f32, 37 | pub hidden: Option, 38 | } 39 | 40 | #[derive(Deserialize)] 41 | pub struct RowData { 42 | pub height: f32, 43 | pub hidden: Option, 44 | } 45 | 46 | #[derive(Deserialize)] 47 | pub struct CellCoords { 48 | pub column: u32, 49 | pub row: u32, 50 | } 51 | 52 | #[derive(Deserialize)] 53 | pub struct MergedCell { 54 | pub from: CellCoords, 55 | pub to: CellCoords, 56 | } 57 | 58 | #[derive(Deserialize)] 59 | pub struct Cell { 60 | pub v: Option, 61 | pub s: Option, 62 | pub hyperlink: Option, 63 | } 64 | 65 | #[derive(Deserialize)] 66 | pub struct ValidationSource { 67 | pub r#type: String, 68 | pub value: Value, 69 | } 70 | 71 | #[derive(Deserialize)] 72 | pub struct DataValidation { 73 | pub range: String, 74 | pub source: ValidationSource, 75 | } 76 | 77 | #[derive(Deserialize)] 78 | pub struct SheetData { 79 | name: Option, 80 | cells: Option>>>, 81 | plain: Option>>>, 82 | cols: Option>>, 83 | rows: Option>>, 84 | merged: Option>, 85 | frozen_rows: Option, 86 | frozen_cols: Option, 87 | validations: Option>, 88 | } 89 | 90 | #[derive(Deserialize)] 91 | pub struct SpreadsheetData { 92 | data: Vec, 93 | styles: Option>, 94 | } 95 | 96 | struct InnerCell { 97 | cell: String, 98 | value: CellValue, 99 | style: Option 100 | } 101 | 102 | #[derive(Clone)] 103 | struct Hyperlink { 104 | cell: String, 105 | url: String, 106 | } 107 | impl InnerCell { 108 | pub fn new(cell: String, style: &Option) -> InnerCell { 109 | InnerCell { 110 | cell, 111 | value: CellValue::None, 112 | style: style.to_owned() 113 | } 114 | } 115 | } 116 | 117 | enum CellValue { 118 | None, 119 | Value(String), 120 | Formula(String), 121 | SharedString(u32) 122 | } 123 | 124 | #[wasm_bindgen] 125 | pub fn import_to_xlsx(raw_data: &JsValue) -> Vec { 126 | utils::set_panic_hook(); 127 | 128 | let data: SpreadsheetData = raw_data.into_serde().unwrap(); 129 | let futures = formulas::get_future_functions(); 130 | 131 | let mut shared_strings = vec!(); 132 | let mut shared_strings_count = 0; 133 | let style_table = StyleTable::new(data.styles); 134 | 135 | let mut sheets_info: Vec<(String, String)> = vec!(); 136 | 137 | let buf: Vec = vec!(); 138 | let w = Cursor::new(buf); 139 | let mut zip = zip::ZipWriter::new(w); 140 | let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored).unix_permissions(0o755); 141 | for (sheet_index, sheet) in data.data.iter().enumerate() { 142 | let mut rows: Vec> = vec!(); 143 | let mut hyperlinks: Vec = vec![]; 144 | match &sheet.cells { 145 | Some(cells) => { 146 | for (row_index, row) in cells.iter().enumerate() { 147 | let mut inner_row: Vec = vec!(); 148 | for (col_index, cell) in row.iter().enumerate() { 149 | match cell { 150 | Some(cell) => { 151 | let cell_name = cell_offsets_to_index(row_index, col_index); 152 | let mut inner_cell = InnerCell::new(cell_name, &cell.s); 153 | if let Some(link) = &cell.hyperlink { 154 | hyperlinks.push(Hyperlink { 155 | cell: inner_cell.cell.clone(), 156 | url: link.clone(), 157 | }); 158 | } 159 | match &cell.v { 160 | Some(value) => { 161 | if !value.is_empty() { 162 | match value.parse::() { 163 | Ok(_) if !value.starts_with("0") || value.len() == 1 => { 164 | inner_cell.value = CellValue::Value(value.to_owned()); 165 | }, 166 | Err(_) | _ => { 167 | if value.starts_with("=") { 168 | // [FIXME] formula can be corrupted 169 | inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures)); 170 | } else { 171 | shared_strings_count += 1; 172 | // [FIXME] N^2, slow 173 | match shared_strings.iter().position(|s| s == value) { 174 | Some(index) => { 175 | inner_cell.value = CellValue::SharedString(index as u32); 176 | }, 177 | None => { 178 | inner_cell.value = CellValue::SharedString(shared_strings.len() as u32); 179 | shared_strings.push(value.to_owned()); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | None => () 188 | } 189 | 190 | inner_row.push(inner_cell); 191 | }, 192 | None => () 193 | } 194 | } 195 | rows.push(inner_row); 196 | } 197 | }, 198 | None => { 199 | match &sheet.plain { 200 | Some(plain) => { 201 | for (row_index, row) in plain.iter().enumerate() { 202 | let mut inner_row: Vec = vec!(); 203 | for (col_index, cell) in row.iter().enumerate() { 204 | match cell { 205 | Some(value) => { 206 | let cell_name = cell_offsets_to_index(row_index, col_index); 207 | let mut inner_cell = InnerCell::new(cell_name, &None); 208 | match value.parse::() { 209 | Ok(_) if !value.starts_with("0") || value.len() == 1 => { 210 | inner_cell.value = CellValue::Value(value.to_owned()); 211 | }, 212 | Err(_) | _ => { 213 | if value.starts_with("=") { 214 | // [FIXME] formula can be corrupted 215 | inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures)); 216 | } else { 217 | inner_cell.value = CellValue::SharedString(shared_strings.len() as u32); 218 | shared_strings.push(value.to_owned()); 219 | } 220 | } 221 | } 222 | inner_row.push(inner_cell); 223 | }, 224 | None => () 225 | } 226 | } 227 | rows.push(inner_row); 228 | } 229 | }, 230 | None => () 231 | } 232 | } 233 | } 234 | 235 | let sheet_info = get_sheet_info(sheet.name.clone(), sheet_index); 236 | let rels_path = format!("xl/worksheets/_rels/sheet{}.xml.rels", sheet_index + 1); 237 | zip.start_file(rels_path.clone(), options).unwrap(); 238 | zip.write_all(get_sheet_rels(&hyperlinks).as_bytes()).unwrap(); 239 | zip.start_file(sheet_info.0.clone(), options).unwrap(); 240 | zip.write_all( 241 | get_sheet_data( 242 | rows, 243 | &sheet.cols, 244 | &sheet.rows, 245 | &sheet.merged, 246 | sheet.frozen_rows, 247 | sheet.frozen_cols, 248 | &sheet.validations, 249 | &hyperlinks, 250 | ) 251 | .as_bytes(), 252 | ).unwrap(); 253 | sheets_info.push(sheet_info); 254 | } 255 | 256 | zip.start_file("_rels/.rels", options).unwrap(); 257 | zip.write_all(ROOT_RELS).unwrap(); 258 | 259 | let (content_types, rels, workbook) = get_nav(sheets_info); 260 | 261 | zip.start_file("[Content_Types].xml", options).unwrap(); 262 | zip.write_all(content_types.as_bytes()).unwrap(); 263 | 264 | zip.start_file("xl/_rels/workbook.xml.rels", options).unwrap(); 265 | zip.write_all(rels.as_bytes()).unwrap(); 266 | zip.start_file("xl/workbook.xml", options).unwrap(); 267 | zip.write_all(workbook.as_bytes()).unwrap(); 268 | zip.start_file("xl/sharedStrings.xml", options).unwrap(); 269 | zip.write_all(get_shared_strings_data(shared_strings, shared_strings_count).as_bytes()).unwrap(); 270 | 271 | zip.start_file("xl/styles.xml", options).unwrap(); 272 | zip.write_all(get_styles_data(style_table).as_bytes()).unwrap(); 273 | 274 | let res = zip.finish().unwrap(); 275 | res.get_ref().to_vec() 276 | } 277 | 278 | fn get_styles_data(style_table: StyleTable) -> String { 279 | let mut style_sheet = Element::new("styleSheet"); 280 | 281 | let mut formats_element = Element::new("numFmts"); 282 | let mut formats_children = vec!(); 283 | for (format, index) in &style_table.custom_formats { 284 | let mut format_element = Element::new("numFmt"); 285 | format_element 286 | .add_attr("formatCode", format) 287 | .add_attr("numFmtId", index.to_string()); 288 | formats_children.push(format_element); 289 | } 290 | formats_element 291 | .add_attr("count", style_table.custom_formats.len().to_string()) 292 | .add_children(formats_children); 293 | 294 | let mut fonts_element = Element::new("fonts"); 295 | let fonts_children: Vec = style_table.fonts.iter().map(|ref font| { 296 | let mut font_element = Element::new("font"); 297 | let mut font_children = vec!(); 298 | match font.name { 299 | Some(ref font) => { 300 | let mut name_el = Element::new("name"); 301 | name_el.add_attr("val", font); 302 | font_children.push(name_el); 303 | } 304 | None => (), 305 | } 306 | match font.size { 307 | Some(ref size) => { 308 | let mut sz = Element::new("sz"); 309 | sz.add_attr("val", size.to_string()); 310 | font_children.push(sz); 311 | }, 312 | None => () 313 | } 314 | match font.color { 315 | Some(ref color) => { 316 | let mut color_element = Element::new("color"); 317 | color_element.add_attr("rgb", color); 318 | font_children.push(color_element); 319 | }, 320 | None => () 321 | } 322 | if font.bold { 323 | let b = Element::new("b"); 324 | font_children.push(b); 325 | } 326 | if font.italic { 327 | let i = Element::new("i"); 328 | font_children.push(i); 329 | } 330 | if font.underline { 331 | let u = Element::new("u"); 332 | font_children.push(u); 333 | } 334 | if font.strike { 335 | let s = Element::new("strike"); 336 | font_children.push(s); 337 | } 338 | font_element.add_children(font_children); 339 | font_element 340 | }).collect(); 341 | 342 | fonts_element 343 | .add_attr("count", style_table.fonts.len().to_string()) 344 | .add_children(fonts_children); 345 | 346 | let mut borders_element = Element::new("borders"); 347 | let border_children: Vec = style_table.borders.iter().map(|border| { 348 | let mut border_element = Element::new("border"); 349 | let mut children: Vec = vec![]; 350 | 351 | if let Some(b) = &border.left { 352 | children.push(b.to_xml_el()) 353 | } 354 | if let Some(b) = &border.right { 355 | children.push(b.to_xml_el()) 356 | } 357 | if let Some(b) = &border.top { 358 | children.push(b.to_xml_el()) 359 | } 360 | if let Some(b) = &border.bottom { 361 | children.push(b.to_xml_el()) 362 | } 363 | 364 | border_element.add_children(children); 365 | border_element 366 | }).collect(); 367 | borders_element.add_attr("count", border_children.len().to_string()); 368 | borders_element.add_children(vec![Element::new("border")]); 369 | borders_element.add_children(border_children); 370 | 371 | 372 | let mut fills_element = Element::new("fills"); 373 | let fills_children: Vec = style_table.fills.iter().map(|ref fill| { 374 | let mut fill_element = Element::new("fill"); 375 | let mut pattern_fill = Element::new("patternFill"); 376 | pattern_fill.add_attr("patternType", &fill.pattern_type); 377 | fill.color.clone().map(|color| { 378 | let mut fg_color = Element::new("fgColor"); 379 | let mut bg_color = Element::new("bgColor"); 380 | fg_color.add_attr("rgb", color.clone()); 381 | bg_color.add_attr("rgb", color.clone()); 382 | pattern_fill.add_children(vec![fg_color, bg_color]); 383 | }); 384 | fill_element.add_children(vec![pattern_fill]); 385 | fill_element 386 | }).collect(); 387 | 388 | fills_element 389 | .add_attr("count", style_table.fills.len().to_string()) 390 | .add_children(fills_children); 391 | 392 | let mut cell_xfs = Element::new("cellXfs"); 393 | let xfs_children: Vec = style_table.xfs.iter().map(|p| { 394 | let mut xf = Element::new("xf"); 395 | 396 | p.font_id.map(|id|{ 397 | xf 398 | .add_attr("applyFont", "1") 399 | .add_attr("fontId", id.to_string()); 400 | }); 401 | p.fill_id.map(|id| { 402 | xf 403 | .add_attr("applyFill", "1") 404 | .add_attr("fillId", id.to_string()); 405 | }); 406 | p.format_id.map(|id| { 407 | xf 408 | .add_attr("applyNumberFormat", "1") 409 | .add_attr("numFmtId", id.to_string()); 410 | }); 411 | p.border_id.map(|id| { 412 | xf 413 | .add_attr("applyBorder", "1") 414 | .add_attr("borderId", id.to_string()); 415 | }); 416 | if p.align_h.is_some() || p.align_v.is_some() || p.wrap_text { 417 | let mut alignment = Element::new("alignment"); 418 | 419 | p.align_h.as_ref().map(|v| alignment.add_attr("horizontal", v)); 420 | p.align_v.as_ref().map(|v| alignment.add_attr("vertical", v)); 421 | if p.wrap_text { 422 | alignment.add_attr("wrapText", "1"); 423 | } 424 | 425 | xf.add_attr("applyAlignment", "1").add_children(vec![alignment]); 426 | } 427 | xf 428 | }).collect(); 429 | 430 | cell_xfs 431 | .add_attr("count", style_table.xfs.len().to_string()) 432 | .add_children(xfs_children); 433 | 434 | style_sheet 435 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 436 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 437 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 438 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 439 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 440 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 441 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 442 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 443 | .add_children(vec![formats_element, fonts_element, fills_element, borders_element, cell_xfs]); 444 | 445 | style_sheet.to_xml() 446 | } 447 | 448 | fn cell_offsets_to_index(row: usize, col: usize) -> String { 449 | let mut num = col; 450 | let mut chars = vec!(); 451 | 452 | while num > 25 { 453 | let part = num % 26; 454 | num = (num - part) / 26 - 1; 455 | chars.push(65u8 + part as u8); 456 | } 457 | chars.push(65u8 + num as u8); 458 | chars.reverse(); 459 | format!("{}{}", String::from_utf8(chars).unwrap(), row + 1) 460 | } 461 | 462 | fn get_sheet_data( 463 | cells: Vec>, 464 | columns: &Option>>, 465 | rows: &Option>>, 466 | merged: &Option>, 467 | frozen_rows: Option, 468 | frozen_cols: Option, 469 | validations: &Option>, 470 | hyperlinks: &[Hyperlink], 471 | ) -> String { 472 | let mut worksheet = Element::new("worksheet"); 473 | let mut sheet_view = Element::new("sheetView"); 474 | 475 | let has_frozen = frozen_cols.unwrap_or(0) > 0 || frozen_rows.unwrap_or(0) > 0; 476 | if has_frozen { 477 | let x_split = frozen_cols.unwrap_or(0); 478 | let y_split = frozen_rows.unwrap_or(0); 479 | let top_left_cell = cell_offsets_to_index( 480 | y_split as usize, 481 | x_split as usize, 482 | ); 483 | 484 | let mut pane = Element::new("pane"); 485 | if x_split > 0 { 486 | pane.add_attr("xSplit", x_split.to_string()); 487 | } 488 | if y_split > 0 { 489 | pane.add_attr("ySplit", y_split.to_string()); 490 | } 491 | 492 | pane.add_attr("topLeftCell", top_left_cell.clone()); 493 | pane.add_attr("state", "frozen"); 494 | 495 | let active_pane = match (x_split > 0, y_split > 0) { 496 | (true, true) => "bottomRight", 497 | (true, false) => "topRight", 498 | (false, true) => "bottomLeft", 499 | _ => "topLeft", 500 | }; 501 | pane.add_attr("activePane", active_pane); 502 | 503 | sheet_view.add_children(vec![pane]); 504 | } 505 | 506 | sheet_view.add_attr("workbookViewId", "0"); 507 | let mut sheet_views = Element::new("sheetViews"); 508 | sheet_views.add_children(vec![sheet_view]); 509 | let mut sheet_format_pr = Element::new("sheetFormatPr"); 510 | sheet_format_pr 511 | .add_attr("customHeight", "1") 512 | .add_attr("defaultRowHeight", "15.75") 513 | .add_attr("defaultColWidth", "14.43"); 514 | 515 | let mut cols = Element::new("cols"); 516 | let mut cols_children = vec!(); 517 | 518 | match columns { 519 | Some(columns) => { 520 | for (index, column) in columns.iter().enumerate() { 521 | match column { 522 | Some(col) => { 523 | let mut column_element = Element::new("col"); 524 | column_element 525 | .add_attr("min", (index + 1).to_string()) 526 | .add_attr("max", (index + 1).to_string()) 527 | .add_attr("customWidth", "1") 528 | .add_attr("width", (col.width / WIDTH_COEF).to_string()); 529 | if let Some(true) = col.hidden { 530 | column_element.add_attr("hidden", "1"); 531 | } 532 | cols_children.push(column_element) 533 | 534 | } 535 | None => () 536 | } 537 | } 538 | }, 539 | None => () 540 | } 541 | let mut rows_info: HashMap = HashMap::new(); 542 | match rows { 543 | Some(rows) => { 544 | for (index, column) in rows.iter().enumerate() { 545 | match column { 546 | Some(row) => { 547 | rows_info.insert(index, row); 548 | }, 549 | None => () 550 | } 551 | } 552 | }, 553 | None => () 554 | } 555 | 556 | let mut sheet_data = Element::new("sheetData"); 557 | let mut sheet_data_rows = vec!(); 558 | for (index, row) in cells.iter().enumerate() { 559 | let mut row_el = Element::new("row"); 560 | row_el.add_attr("r", (index + 1).to_string()); 561 | match rows_info.get(&index) { 562 | Some(row_data) => { 563 | row_el 564 | .add_attr("ht", (row_data.height * HEIGHT_COEF).to_string()) 565 | .add_attr("customHeight", "1"); 566 | if let Some(true) = row_data.hidden { 567 | row_el.add_attr("hidden", "1"); 568 | } 569 | }, 570 | None => () 571 | } 572 | let mut row_cells = vec!(); 573 | for cell in row { 574 | let mut cell_el = Element::new("c"); 575 | cell_el.add_attr("r", &cell.cell); 576 | match &cell.value { 577 | CellValue::Value(ref v) => { 578 | let mut value_cell = Element::new("v"); 579 | value_cell.add_value(v); 580 | cell_el.add_children(vec![value_cell]); 581 | utils::log!("value {}", v) 582 | }, 583 | CellValue::Formula(ref v) => { 584 | let mut value_cell = Element::new("f"); 585 | value_cell.add_value(v); 586 | cell_el.add_children(vec![value_cell]); 587 | utils::log!("formula {}", v) 588 | }, 589 | CellValue::SharedString(ref s) => { 590 | cell_el.add_attr("t", "s"); 591 | let mut value_cell = Element::new("v"); 592 | value_cell.add_value(s.to_string()); 593 | cell_el.add_children(vec![value_cell]); 594 | }, 595 | CellValue::None => () 596 | } 597 | match &cell.style { 598 | Some(ref v) => { 599 | // style index should be incremented as zero style was prepended 600 | cell_el.add_attr("s", (v+1).to_string()); 601 | }, 602 | None => () 603 | } 604 | row_cells.push(cell_el); 605 | } 606 | 607 | row_el.add_children(row_cells); 608 | sheet_data_rows.push(row_el); 609 | } 610 | sheet_data.add_children(sheet_data_rows); 611 | 612 | let mut worksheet_children = vec![sheet_views, sheet_format_pr]; 613 | if cols_children.len() > 0{ 614 | cols.add_children(cols_children); 615 | worksheet_children.push(cols); 616 | } 617 | worksheet_children.push(sheet_data); 618 | 619 | match merged { 620 | Some(merged) => { 621 | if merged.len() > 0 { 622 | let mut merged_cells_element = Element::new("mergeCells"); 623 | merged_cells_element 624 | .add_attr("count", merged.len().to_string()) 625 | .add_children(merged.iter().map(|MergedCell {from, to}| { 626 | let p1 = cell_offsets_to_index(from.row as usize, from.column as usize); 627 | let p2 = cell_offsets_to_index(to.row as usize, to.column as usize); 628 | let cell_ref = format!("{}:{}", p1, p2); 629 | let mut merged_cell = Element::new("mergeCell"); 630 | merged_cell.add_attr("ref", cell_ref); 631 | merged_cell 632 | }).collect()); 633 | worksheet_children.push(merged_cells_element); 634 | } 635 | }, 636 | None => () 637 | } 638 | if let Some(validations) = validations { 639 | if !validations.is_empty() { 640 | let mut dv_el = Element::new("dataValidations"); 641 | dv_el.add_attr("count", validations.len().to_string()); 642 | 643 | let dv_children: Vec = validations.iter().map(|v| { 644 | let mut el = Element::new("dataValidation"); 645 | 646 | el.add_attr("type", "list"); 647 | el.add_attr("sqref", &v.range); 648 | el.add_attr("allowBlank", "1"); 649 | el.add_attr("showDropDown", "0"); 650 | 651 | let mut f1 = Element::new("formula1"); 652 | 653 | match v.source.r#type.as_str() { 654 | "List" => { 655 | if let Some(arr) = v.source.value.as_array() { 656 | let items: Vec = arr.iter().map(|item| { 657 | if let Some(s) = item.as_str() { 658 | s.to_owned() 659 | } else { 660 | item.to_string() 661 | } 662 | }).collect(); 663 | f1.add_value(format!("\"{}\"", items.join(","))); 664 | } else if let Some(s) = v.source.value.as_str() { 665 | // Handle string value 666 | f1.add_value(format!("\"{}\"", s)); 667 | } 668 | }, 669 | "RangeReference" => { 670 | if let Some(s) = v.source.value.as_str() { 671 | f1.add_value(s); 672 | } 673 | }, 674 | "StringList" => { 675 | if let Some(s) = v.source.value.as_str() { 676 | f1.add_value(format!("\"{}\"", s)); 677 | } 678 | }, 679 | _ => { 680 | if let Some(s) = v.source.value.as_str() { 681 | f1.add_value(s); 682 | } 683 | } 684 | } 685 | 686 | el.add_children(vec![f1]); 687 | el 688 | }).collect(); 689 | 690 | dv_el.add_children(dv_children); 691 | worksheet_children.push(dv_el); 692 | } 693 | } 694 | if !hyperlinks.is_empty() { 695 | let mut hyperlinks_el = Element::new("hyperlinks"); 696 | let mut hyperlink_children = vec![]; 697 | 698 | for (i, h) in hyperlinks.iter().enumerate() { 699 | let mut hyperlink_el = Element::new("hyperlink"); 700 | hyperlink_el 701 | .add_attr("r:id", format!("rId{}", 1 + i)) 702 | .add_attr("ref", &h.cell); 703 | hyperlink_children.push(hyperlink_el); 704 | } 705 | 706 | hyperlinks_el.add_children(hyperlink_children); 707 | worksheet_children.push(hyperlinks_el); 708 | } 709 | 710 | worksheet 711 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 712 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 713 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 714 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 715 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 716 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 717 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 718 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 719 | .add_children(worksheet_children); 720 | 721 | worksheet.to_xml() 722 | } 723 | 724 | fn get_shared_strings_data(shared_strings: Vec, shared_strings_count: i32) -> String { 725 | let mut sst = Element::new("sst"); 726 | let sst_children: Vec = shared_strings.iter().map(|s| { 727 | let mut t = Element::new("t"); 728 | t.add_value(s); 729 | let mut si = Element::new("si"); 730 | si.add_children(vec![t]); 731 | si 732 | }).collect(); 733 | 734 | sst 735 | .add_attr("uniqueCount", shared_strings.len().to_string()) 736 | .add_attr("count", shared_strings_count.to_string()) 737 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 738 | .add_children(sst_children); 739 | 740 | sst.to_xml() 741 | } 742 | 743 | fn get_sheet_info(name: Option, index: usize) -> (String, String) { 744 | let sheet_name = name.unwrap_or(format!("sheet{}", index + 1)); 745 | (format!("xl/worksheets/sheet{}.xml", index + 1), sheet_name) 746 | } 747 | 748 | fn get_sheet_rels(hyperlinks: &[Hyperlink]) -> String { 749 | let mut rels = Element::new("Relationships"); 750 | rels.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); 751 | 752 | let mut children = vec![]; 753 | 754 | for (i, link) in hyperlinks.iter().enumerate() { 755 | let mut rel = Element::new("Relationship"); 756 | rel.add_attr("Id", format!("rId{}", 1 + i)); 757 | rel.add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"); 758 | rel.add_attr("Target", &link.url); 759 | rel.add_attr("TargetMode", "External"); 760 | children.push(rel); 761 | } 762 | 763 | rels.add_children(children); 764 | rels.to_xml() 765 | } 766 | 767 | fn get_nav(sheets: Vec<(String, String)>) -> (String, String, String) { 768 | let mut content_types = Element::new("Types"); 769 | content_types.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/content-types"); 770 | 771 | let mut overrides = vec!(); 772 | 773 | let mut default_xml = Element::new("Default"); 774 | default_xml 775 | .add_attr("ContentType", "application/xml") 776 | .add_attr("Extension", "xml"); 777 | let mut default_rels = Element::new("Default"); 778 | default_rels 779 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 780 | .add_attr("Extension", "rels"); 781 | 782 | let mut root_rels = Element::new("Override"); 783 | root_rels 784 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 785 | .add_attr("PartName", "/_rels/.rels"); 786 | let mut root_workbook_rels = Element::new("Override"); 787 | root_workbook_rels 788 | .add_attr("ContentType", "application/vnd.openxmlformats-package.relationships+xml") 789 | .add_attr("PartName", "/xl/_rels/workbook.xml.rels"); 790 | let mut shared_strings_rels = Element::new("Override"); 791 | shared_strings_rels 792 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml") 793 | .add_attr("PartName", "/xl/sharedStrings.xml"); 794 | let mut style_rels = Element::new("Override"); 795 | style_rels 796 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml") 797 | .add_attr("PartName", "/xl/styles.xml"); 798 | let mut workbook_rels = Element::new("Override"); 799 | workbook_rels 800 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml") 801 | .add_attr("PartName", "/xl/workbook.xml"); 802 | 803 | overrides.push(default_xml); 804 | overrides.push(default_rels); 805 | overrides.push(root_rels); 806 | overrides.push(root_workbook_rels); 807 | overrides.push(shared_strings_rels); 808 | for (path, _) in sheets.iter() { 809 | let mut sheet_rel = Element::new("Override"); 810 | sheet_rel 811 | .add_attr("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml") 812 | .add_attr("PartName", String::from("/") + path); 813 | overrides.push(sheet_rel); 814 | } 815 | overrides.push(style_rels); 816 | overrides.push(workbook_rels); 817 | 818 | content_types.add_children(overrides); 819 | 820 | let mut relationships = Element::new("Relationships"); 821 | relationships.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); 822 | let mut relationships_children = vec!(); 823 | 824 | let mut style_relationship = Element::new("Relationship"); 825 | style_relationship 826 | .add_attr("Id", "rId1") 827 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles") 828 | .add_attr("Target", "styles.xml"); 829 | let mut shared_string_relationship = Element::new("Relationship"); 830 | shared_string_relationship 831 | .add_attr("Id", "rId2") 832 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings") 833 | .add_attr("Target", "sharedStrings.xml"); 834 | 835 | relationships_children.push(style_relationship); 836 | relationships_children.push(shared_string_relationship); 837 | 838 | let mut workbook = Element::new("workbook"); 839 | let mut workbook_children = vec!(); 840 | workbook_children.push(Element::new("workbookPr")); 841 | let mut sheets_element = Element::new("sheets"); 842 | let mut sheet_children = vec!(); 843 | 844 | let mut last_id = 3; 845 | for (index, (_, name)) in sheets.iter().enumerate() { 846 | let mut sheet_relationship = Element::new("Relationship"); 847 | sheet_relationship 848 | .add_attr("Id", format!("rId{}", last_id)) 849 | .add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet") 850 | .add_attr("Target", format!("worksheets/sheet{}.xml", (index+1))); 851 | relationships_children.push(sheet_relationship); 852 | let mut sheet_element = Element::new("sheet"); 853 | sheet_element 854 | .add_attr("r:id", format!("rId{}", last_id)) 855 | .add_attr("name", name) 856 | .add_attr("sheetId", (index+1).to_string()); 857 | 858 | if index == 0 { 859 | sheet_element.add_attr("state", "visible"); 860 | } 861 | sheet_children.push(sheet_element); 862 | 863 | last_id += 1; 864 | } 865 | relationships.add_children(relationships_children); 866 | sheets_element.add_children(sheet_children); 867 | workbook_children.push(sheets_element); 868 | workbook_children.push(Element::new("definedNames")); 869 | workbook_children.push(Element::new("calcPr")); 870 | 871 | workbook 872 | .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main") 873 | .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac") 874 | .add_attr("xmlns:x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main") 875 | .add_attr("xmlns:mv", "urn:schemas-microsoft-com:mac:vml") 876 | .add_attr("xmlns:mc", "http://schemas.openxmlformats.org/markup-compatibility/2006") 877 | .add_attr("xmlns:mx", "http://schemas.microsoft.com/office/mac/excel/2008/main") 878 | .add_attr("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") 879 | .add_attr("xmlns", "http://schemas.openxmlformats.org/spreadsheetml/2006/main") 880 | .add_children(workbook_children); 881 | 882 | (content_types.to_xml(), relationships.to_xml(), workbook.to_xml()) 883 | } --------------------------------------------------------------------------------