├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── @rsw ├── chasm │ ├── Cargo.toml │ └── src │ │ ├── basic.frag │ │ ├── basic.vert │ │ └── lib.rs ├── excel-read │ ├── Cargo.toml │ ├── README.md │ ├── calamine │ │ ├── .clippy.toml │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── Cargo.toml │ │ ├── Changelog.md │ │ ├── LICENSE-MIT.md │ │ ├── README.md │ │ ├── appveyor.yml │ │ ├── benches │ │ │ └── basic.rs │ │ ├── examples │ │ │ ├── excel_to_csv.rs │ │ │ └── search_errors.rs │ │ └── src │ │ │ ├── auto.rs │ │ │ ├── cfb.rs │ │ │ ├── datatype.rs │ │ │ ├── de.rs │ │ │ ├── errors.rs │ │ │ ├── lib.rs │ │ │ ├── ods.rs │ │ │ ├── utils.rs │ │ │ ├── vba.rs │ │ │ ├── xls.rs │ │ │ ├── xlsb.rs │ │ │ └── xlsx.rs │ ├── src │ │ ├── excel.rs │ │ ├── lib.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs ├── fractals │ ├── Cargo.toml │ └── src │ │ └── main.rs └── game-of-life │ ├── Cargo.toml │ └── src │ └── lib.rs ├── README.md ├── assets ├── chasm │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png └── game-of-life │ ├── 1.png │ └── 2.png ├── awesome-lists.md ├── index.html ├── package.json ├── public ├── CNAME └── favicon.ico ├── rsw.toml ├── src ├── App.tsx ├── assets │ └── awesome-rsw.svg ├── index.scss ├── main.tsx ├── router │ ├── RouteWithSubRoutes.tsx │ ├── Router.tsx │ └── types.ts ├── routes.tsx └── views │ ├── chasm.tsx │ ├── excel_read.tsx │ ├── ffmpeg.tsx │ ├── game_of_life.tsx │ └── home.tsx ├── tsconfig.json ├── vercel.json └── vite.config.ts /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '14' 17 | 18 | # Install wasm-pack 19 | - uses: jetli/wasm-pack-action@v0.3.0 20 | with: 21 | # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') 22 | version: v0.9.1 23 | 24 | - name: Install rsw 25 | run: cargo install rsw 26 | 27 | - run: yarn 28 | - run: yarn build 29 | 30 | - name: Deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GH_TOKEN }} 34 | publish_dir: ./dist -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .history 3 | 4 | node_modules 5 | # dist 6 | *.local 7 | *.lock 8 | package-lock.json 9 | 10 | # rust 11 | target/ 12 | Cargo.lock 13 | pkg/ 14 | 15 | /libs 16 | /dist 17 | /.rsw -------------------------------------------------------------------------------- /@rsw/chasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chasm" 3 | version = "0.1.0" 4 | authors = ["lencx "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | wasm-bindgen = "0.2.70" 13 | js-sys = "0.3.46" 14 | 15 | [dependencies.web-sys] 16 | version = "0.3.51" 17 | features = [ 18 | 'console', 19 | 'MouseEvent', 20 | 'Document', 21 | 'Element', 22 | 'HtmlCanvasElement', 23 | 'WebGlBuffer', 24 | 'WebGlRenderingContext', 25 | 'WebGlUniformLocation', 26 | 'WebGlProgram', 27 | 'WebGlShader', 28 | 'Window', 29 | ] -------------------------------------------------------------------------------- /@rsw/chasm/src/basic.frag: -------------------------------------------------------------------------------- 1 | // choose flot precision 2 | precision mediump float; 3 | 4 | // uniform values 5 | uniform vec2 u_viewport; 6 | uniform vec2 u_juliaComplex; 7 | 8 | // function to map from a range to another (I know there's an extra pair of parenthesis but it makes the operation easier to understand) 9 | float map (float v, float inMin, float inMax, float outMin, float outMax) { 10 | return ((v - inMin) * ((outMax - outMin) / (inMax - inMin))) + outMin; 11 | } 12 | 13 | // set the opacity of every pixel by checking if any of its position's components, mapped to the complex plane, tend to infinity or not after applying recursively to them the function f(z) = z^2 + c (c being the mouse's position's x and y coordinates, relative to the viewport, mapped to -1.4 -> 1.4 range and -2 -> 2 respectively; this additional mapping is not necessary but I simply used it for aesthetic purposes) 14 | void main() { 15 | // check if any of the components of the current complex number tend to infinity (and if so after how many iterations) 16 | float real = map(gl_FragCoord.x, 0.0, u_viewport.x, -2.0, 2.0); 17 | float imaginary = map(gl_FragCoord.y, 0.0, u_viewport.y, 1.4, -1.4); 18 | float realSquared = real * real; 19 | float imaginarySquared = imaginary * imaginary; 20 | int iterationCount; 21 | for(int i = 0; i < 255; i++){ 22 | imaginary = 2.0 * real * imaginary + map(u_juliaComplex.y, 0.0, u_viewport.y, .8, -.8); 23 | real = realSquared - imaginarySquared + map(u_juliaComplex.x, 0.0, u_viewport.x, -.9, .4); 24 | realSquared = real * real; 25 | imaginarySquared = imaginary * imaginary; 26 | iterationCount = i; 27 | if( abs((imaginarySquared) / (imaginary + real)) > 10.0 ){ break; } 28 | } 29 | // set pixels color 30 | if (iterationCount == 254){ 31 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 32 | } else { 33 | gl_FragColor = vec4(0.0, 0.0, 0.0, map(float(iterationCount), 0.0, 253.0, 0.0, 1.0) + .75); 34 | } 35 | } -------------------------------------------------------------------------------- /@rsw/chasm/src/basic.vert: -------------------------------------------------------------------------------- 1 | // vertex position attribure 2 | attribute vec2 a_Position; 3 | 4 | // set vertex position 5 | void main() { 6 | gl_Position = vec4(a_Position, 0, 1); 7 | } -------------------------------------------------------------------------------- /@rsw/chasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use wasm_bindgen::prelude::*; 4 | use wasm_bindgen::JsCast; 5 | use web_sys::WebGlRenderingContext as GL; 6 | 7 | #[wasm_bindgen] 8 | pub fn chasm(canvas_id: &str) -> Result<(), JsValue> { 9 | let window = window(); 10 | let document = document(); 11 | let chasm = document.get_element_by_id(canvas_id).unwrap(); 12 | let canvas: web_sys::HtmlCanvasElement = chasm.dyn_into::()?; 13 | 14 | // get window width & height 15 | // https://github.com/rustwasm/wasm-bindgen/issues/2231 16 | let win_width = window.inner_width()?.as_f64().ok_or(JsValue::NULL)?; 17 | let win_height = window.inner_height()?.as_f64().ok_or(JsValue::NULL)?; 18 | 19 | // fit the canvas to the viewport 20 | canvas.set_width(win_width as u32); 21 | canvas.set_height(win_height as u32); 22 | 23 | let gl = canvas.get_context("webgl")?.unwrap().dyn_into::()?; 24 | 25 | gl.viewport(0, 0, canvas.width() as i32, canvas.height() as i32); 26 | 27 | let vert_code = include_str!("./basic.vert"); 28 | let frag_code = include_str!("./basic.frag"); 29 | 30 | // create vert_shader 31 | let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap(); 32 | gl.shader_source(&vert_shader, &vert_code); 33 | gl.compile_shader(&vert_shader); 34 | 35 | // create frag_shader 36 | let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap(); 37 | gl.shader_source(&frag_shader, &frag_code); 38 | gl.compile_shader(&frag_shader); 39 | 40 | // create shader program 41 | let program = gl.create_program().unwrap(); 42 | gl.attach_shader(&program, &vert_shader); 43 | gl.attach_shader(&program, &frag_shader); 44 | gl.link_program(&program); 45 | 46 | gl.use_program(Some(&program)); 47 | 48 | // get the locations of attributes (and activate their address in the vertex attributes array) and uniforms 49 | // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getAttribLocation 50 | let vertex_position_location = gl.get_attrib_location(&program, "a_Position") as u32; 51 | let julia_complex_uniform_location = gl.get_uniform_location(&program, "u_juliaComplex"); 52 | gl.enable_vertex_attrib_array(vertex_position_location as u32); 53 | 54 | let viewport_uniform_location = gl.get_uniform_location(&program, "u_viewport"); 55 | 56 | let buffer = gl.create_buffer().unwrap(); 57 | gl.bind_buffer(GL::ARRAY_BUFFER, Some(&buffer)); 58 | 59 | // create the buffer that the GPU will get the vertex data from 60 | let vertices: Vec = vec![-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]; 61 | let vert_array = js_sys::Float32Array::from(vertices.as_slice()); 62 | 63 | gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &vert_array, GL::STATIC_DRAW); 64 | 65 | gl.uniform2f( 66 | Some(viewport_uniform_location.as_ref().unwrap()), 67 | win_width as f32, 68 | win_height as f32 69 | ); 70 | 71 | let original_mouse_point_x = Rc::new(RefCell::new((canvas.width() / 2) as f32)); 72 | let original_mouse_point_y = Rc::new(RefCell::new((canvas.height() / 2) as f32)); 73 | 74 | let lerping_factor = 1.0; 75 | 76 | { 77 | // request_animation_frame 78 | let p1_x = original_mouse_point_x.clone(); 79 | let p1_y = original_mouse_point_y.clone(); 80 | 81 | let f = Rc::new(RefCell::new(None)); 82 | let g = f.clone(); 83 | *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { 84 | gl.uniform2f( 85 | Some(julia_complex_uniform_location.as_ref().unwrap()), 86 | *p1_x.borrow() * lerping_factor, 87 | *p1_y.borrow() * lerping_factor, 88 | ); 89 | 90 | // tell webgl where to look for vertex data inside the vertex buffer 91 | gl.vertex_attrib_pointer_with_i32(vertex_position_location, 2, GL::FLOAT, false, 0, 0); 92 | // draw two triangle strips (will form a rectangle) 93 | gl.draw_arrays(GL::TRIANGLE_STRIP, 0, 4); 94 | 95 | // Schedule ourself for another requestAnimationFrame callback. 96 | request_animation_frame(f.borrow().as_ref().unwrap()); 97 | }) as Box)); 98 | 99 | request_animation_frame(g.borrow().as_ref().unwrap()); 100 | } 101 | 102 | { 103 | // mousemove 104 | let p1_x = original_mouse_point_x.clone(); 105 | let p1_y = original_mouse_point_y.clone(); 106 | 107 | let mousemove_cb = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { 108 | *p1_x.borrow_mut() = event.client_x() as f32; 109 | *p1_y.borrow_mut() = event.client_y() as f32; 110 | }) as Box); 111 | 112 | canvas 113 | .add_event_listener_with_callback("mousemove", mousemove_cb.as_ref().unchecked_ref())?; 114 | 115 | mousemove_cb.forget(); 116 | } 117 | 118 | Ok(()) 119 | } 120 | 121 | fn window() -> web_sys::Window { 122 | web_sys::window().expect("no global `window` exists") 123 | } 124 | 125 | fn document() -> web_sys::Document { 126 | window() 127 | .document() 128 | .expect("should have a document on window") 129 | } 130 | 131 | fn request_animation_frame(f: &Closure) { 132 | window() 133 | .request_animation_frame(f.as_ref().unchecked_ref()) 134 | .expect("should register `requestAnimationFrame` OK"); 135 | } 136 | -------------------------------------------------------------------------------- /@rsw/excel-read/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "excel-read" 3 | version = "0.2.0" 4 | authors = [ 5 | "lencx ", # 整理 6 | "郭宇 ", # 作者 7 | ] 8 | edition = "2018" 9 | description = "浏览器端读取excel" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [features] 15 | default = ["console_error_panic_hook"] 16 | 17 | [dependencies] 18 | #common_uu = {git = "https://gitee.com/guoyucode/common_uu.git"} 19 | serde_json = { version = "*", features = ["preserve_order"] } 20 | calamine = { path = "./calamine" } 21 | base64 = "*" 22 | #calamine = "*" 23 | js-sys = "*" 24 | web-sys = { version = "0.3.50", features = ["FileReader", "FileReaderSync", "Blob", "BlobPropertyBag", "File"]} 25 | wasm-bindgen = { version = "0.2.63", features = ["serde-serialize"]} 26 | wasm-bindgen-futures = "0.4.23" 27 | 28 | 29 | # The `console_error_panic_hook` crate provides better debugging of panics by 30 | # logging them with `console.error`. This is great for development, but requires 31 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 32 | # code size when deploying. 33 | console_error_panic_hook = { version = "0.1.6", optional = true } 34 | 35 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size 36 | # compared to the default allocator's ~10K. It is slower than the default 37 | # allocator, however. 38 | # 39 | # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. 40 | wee_alloc = { version = "0.4.5", optional = true } 41 | 42 | [dev-dependencies] 43 | wasm-bindgen-test = "0.3.13" 44 | 45 | [profile.release] 46 | # Tell `rustc` to optimize for small code size. 47 | opt-level = "s" -------------------------------------------------------------------------------- /@rsw/excel-read/README.md: -------------------------------------------------------------------------------- 1 | # excel_read 2 | 3 | > 浏览器端读取excel 4 | 5 | **作者:** 郭宇 6 | [**仓库**: guoyucode/excel_read](https://gitee.com/guoyucode/excel_read) 7 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.41.0" 2 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | .vim 5 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | sudo: false 4 | 5 | arch: 6 | - amd64 7 | - s390x 8 | 9 | rust: 10 | - nightly 11 | - beta 12 | - stable 13 | 14 | before_script: 15 | - export PATH=$HOME/.cargo/bin:$HOME/.local/bin:$PATH 16 | - if [[ $(rustup show active-toolchain) == stable* ]]; then rustup component add rustfmt; fi; 17 | 18 | script: 19 | - cargo build 20 | - cargo test 21 | - if [[ $(rustup show active-toolchain) == stable* ]]; then cargo fmt -- --check; fi; 22 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calamine" 3 | version = "0.18.0" 4 | authors = ["Johann Tuffe "] 5 | repository = "https://github.com/tafia/calamine" 6 | documentation = "https://docs.rs/calamine" 7 | description = "An Excel/OpenDocument Spreadsheets reader and deserializer in pure rust" 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["excel", "vba", "office", "ods", "serde"] 11 | categories = ["encoding", "parsing", "text-processing"] 12 | exclude = ["tests/**/*"] 13 | edition = "2018" 14 | 15 | [badges] 16 | travis-ci = { repository = "tafia/calamine" } 17 | appveyor = { repository = "tafia/calamine" } 18 | 19 | [dependencies] 20 | byteorder = "1.3.4" 21 | codepage = "0.1.1" 22 | encoding_rs = "0.8.24" 23 | log = "0.4.11" 24 | serde = "1.0.116" 25 | quick-xml = { version = "0.19", features = ["encoding"] } 26 | zip = { version = "0.5.8", default-features = false, features = ["deflate"] } 27 | chrono = { version = "0.4.17", features = ["serde"], optional = true } 28 | 29 | [dev-dependencies] 30 | glob = "0.3" 31 | env_logger = "0.7" 32 | 33 | [features] 34 | default = [] 35 | dates = ["chrono"] 36 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/Changelog.md: -------------------------------------------------------------------------------- 1 | > Legend: 2 | - feat: A new feature 3 | - fix: A bug fix 4 | - docs: Documentation only changes 5 | - style: White-space, formatting, missing semi-colons, etc 6 | - refactor: A code change that neither fixes a bug nor adds a feature 7 | - perf: A code change that improves performance 8 | - test: Adding missing tests 9 | - chore: Changes to the build process or auxiliary tools/libraries/documentation 10 | 11 | ## Unreleased 12 | 13 | ## 0.18.0 14 | 15 | - fix: Allow empty value cells in xlsx 16 | - fix: obscure xls parsing errors (#195) 17 | - feat: Improve conversions from raw data to primitives 18 | - docs: fix two typos in readme 19 | - feat: replace macro matches! by match expression to reduce MSRV 20 | 21 | ## 0.17.0 22 | 23 | - feat: use `chunks_exact` instead of chunks where possible 24 | - fix: make `to_u32`, `read_slice` safe and sound 25 | - fix: security issue #199 26 | - test: add regression tests that fail with miri 27 | - feat: detect date/time formatted cells in XLSX 28 | - feat: brute force file detection if extension is not known 29 | - feat: support xlsx sheet sizes beyond u32::MAX 30 | - test: ensure doctest functions actually run 31 | - test: run cargo fmt to fix travis tests 32 | - fix: fix Float reading for XLSB 33 | 34 | ## 0.16.2 35 | - docs: add `deserialize_with` example in readme 36 | - fix: Skip phonetic run 37 | - fix: Fix XLS float parsing error 38 | - docs: Correct MBSC to MBCS in vba.rs (mispelled before) 39 | - style: use 2018 edition paths 40 | - fix: Add the ability to read formula values from XLSB 41 | - fix: support integral date types 42 | 43 | ## 0.16.1 44 | - feat: Make `Metadata.sheets` (and `Reader.sheet_names`) always return names in workbook order 45 | - style: fix warnings in tests 46 | 47 | ## 0.16.0 48 | - feat: deprecate failure and impl `std::error::Error` for all errors. 49 | - feat: add `dates` feature to enrich `DataType` with date conversions fns. 50 | 51 | ## 0.15.6 52 | - feat: update dependencies 53 | 54 | ## 0.15.5 55 | - fix: wrong bound comparisons 56 | 57 | ## 0.15.4 58 | - feat: improve deserializer 59 | - feat: bump dependencies 60 | 61 | ## 0.15.3 62 | - feat: add several new convenient fn to `DataType` 63 | - feat: add a `Range::range` fn to get subranges 64 | - feat: add a new `Range::cells` iterator 65 | - feat: impl DoubleEndedIterator when possible 66 | - perf: add some missing `size_hint` impl in iterators 67 | - feat: add a `Range::get` fn (similar to slice's) 68 | - perf: add some `ExactSizeIterator` 69 | 70 | ## 0.15.2 71 | - feat: consider empty cell as empty str if deserializing to str or String 72 | 73 | ## 0.15.1 74 | - fix: xls - allow sectors ending after eof (truncate them!) 75 | 76 | ## 0.15.0 77 | - feat: codepage/encoding_rs for codpage mapping 78 | 79 | ## 0.14.10 80 | - fix: serde map do not stop at first empty value 81 | 82 | ## 0.14.9 83 | - fix: do not return map keys for empty cells. Fixes not working `#[serde(default)]` 84 | 85 | ## 0.14.8 86 | - feat: bump dependencies 87 | - feat: add a `RangeDeserializerBuilder::with_headers` fn to improve serde deserializer 88 | 89 | ## 0.14.7 90 | - feat: ods, support *text:s* and *text:p* 91 | 92 | ## 0.14.6 93 | - fix: support MulRk for xls files 94 | 95 | ## 0.14.5 96 | - fix: properly parse richtext ods files 97 | - refactor: bump dependencies 98 | 99 | ## 0.14.4 100 | - feat: ods: display sheet names in order. 101 | 102 | ## 0.14.3 103 | - feat: handle 'covered cells' which are behind merge-cells in ODS 104 | 105 | ## 0.14.2 106 | - fix: boolean detection and missing repeated cells in ODS 107 | - refactor: bump dependencies 108 | 109 | ## 0.14.1 110 | - fix: possibility of index out of bound in get_value and eventually in Index<(usize, usize)> 111 | 112 | ## 0.14.0 113 | - feat: have Range `start`/`end` return None if the range is actually empty 114 | - feat: Have `Range::get_value` return an Option if the index is out of range 115 | 116 | ## 0.13.1 117 | - refactor: bump dependencies 118 | - feat: make `Range::from_sparse` public 119 | 120 | ## 0.13.0 121 | - feat: migrate from error-chain to failure 122 | - refactor: simplify Reader trait (enable direct Xlsx read etc ...) 123 | - refactor: always initialize at creation 124 | - feat: more documentation on error 125 | - feat: bump dependencies (calamine, encoding_rs and zip) 126 | - feat: process any Read not only Files 127 | - docs: fix various typos 128 | 129 | ## 0.12.1 130 | - feat: update dependencies 131 | 132 | ## 0.12.0 133 | - feat: add serde deserialization 134 | 135 | ## 0.11.8 136 | - perf: update dependencies, in particular quick-xml 0.9.1 137 | 138 | ## 0.11.7 139 | - fix: add a bound check when decoding cfb 140 | - refactor: bump dependencies 141 | 142 | ## 0.11.6 143 | - refactor: bump dependencies 144 | - style: ignore .bk files 145 | 146 | ## 0.11.5 147 | - refactor: bump dependencies 148 | 149 | ## 0.11.4 150 | - refactor: update to quick-xml 0.7.3 and encoding_rs 0.6.6 151 | 152 | ## 0.11.3 153 | - feat: implement Display for DataType and CellTypeError 154 | - feat: add a CellType alias trait 155 | 156 | ## 0.11.2 157 | - perf: update to quick-xml 0.7.1 158 | 159 | ## 0.11.1 160 | - refactor: update encoding_rs to 0.6.2 161 | - perf: add benches and avoid clearing a buffer supposed to be reused 162 | 163 | ## 0.11.0 164 | - feat: add support for formula parsing/decoding 165 | - refactor: make `Range` generic over its content 166 | - fix: convert codepage 21010 as codepage 1200 167 | - fix: support EUC_KR encoding 168 | 169 | ## 0.10.2 170 | - fix: error while using a singlebyte encoding for xls files (read_dbcs) 171 | 172 | ## 0.10.1 173 | - fix: error while using a singlebyte encoding for xls files (short_strings) 174 | 175 | ## 0.10.0 176 | - feat: support defined names for named ranges 177 | - refactor: better internal logics 178 | 179 | ## 0.9.0 180 | - refactor: rename `Excel` in `Sheets` to accomodate OpenDocuments 181 | - feat: add Index/IndexMut for Range 182 | 183 | ## 0.8.0 184 | - feat: add basic support for opendocument spreadsheets 185 | - style: apply rustfmt 186 | - feat: force rustfmt on travis checks 187 | 188 | ## 0.7.0 189 | - fix: extend appveyor paths to be able to use curl 190 | - refactor: update deps 191 | - fix: extract richtext reading from `read_shared_strings` to `read_string`, 192 | and use for inlineStr instead of `read_inline_str` 193 | - style: rustfmt 194 | - fix: enable namespaced xmls when parsing xlsx files 195 | 196 | ## 0.6.0 197 | - refactor: bump dependencies 198 | - refactor: move from rust-encoding to encoding_rs (faster), loses some decoders ... 199 | 200 | ## 0.5.1 201 | - refactor: bump to quick-xml 0.6.0 (supposedly faster) 202 | 203 | ## 0.5.0 204 | - style: rustfmt the code 205 | - feat: xlsx - support 'inlineStr' elements (`` nodes) 206 | - fix: xlsx - support sheetnames prefixed with 'xl/' or '/xl/' 207 | - chore: bump deps (error-chain 0.8.1, quick-xml 0.5.0) 208 | 209 | ## 0.4.0 210 | - refactor: replace `try!` with `?` operator 211 | - feat: adds a new `worksheet_range_by_index` function. 212 | - feat: adds new `ErrorKind`s 213 | - refactor: simplify `search_error` example by using a `run()` function 214 | 215 | ## 0.3.3 216 | - refactor: update dependencies (error-chain and byteorder) 217 | 218 | ## 0.3.2 219 | - refactor: update dependencies 220 | 221 | ## 0.3.1 222 | - perf: [xls] preload vba only instead of sheets only 223 | - refactor: [vba] consume cfb in constructor and do not store cfb 224 | 225 | ## 0.3.0 226 | - feat: [all] better `Range` initialization via `Range::from_sparse` 227 | - feat: [all] several new fn in `Range` (`used_cells`, `start`, `end` ...) 228 | - refactor: adds a `range_eq!` macro in tests 229 | 230 | ## 0.2.1 231 | - fix: [xls] allow directory start to empty sector if version = 3 232 | - fix: [vba] support all project codepage encodings 233 | - feat: [xls] early exit if workbook is password protected 234 | - fix: [xls] better decoding based on codepage 235 | - fix: [xlsb] simplify setting values and early exit when stepping into an invalid BrtRowHdr 236 | - fix: [xlsb] fix record length calculation 237 | 238 | ## 0.2.0 239 | - fix: [all] allow range to resize when we try to set a value out of bounds 240 | - docs: less `unwrap`s, no unused imports 241 | - refactor: range bounds is not (`start`, `end`) instead of (`position`, `size`) 242 | - feat: add new methods for `Range`: `width`, `height`, `is_empty` 243 | 244 | ## 0.1.3 245 | - fix: [xls] better management of continue record for rich_extended_strings 246 | 247 | ## 0.1.2 248 | - fix: [all] return error when trying to set out of bound values in `Range` 249 | - fix: [xls] do a proper encoding when reading cells (force 2 bytes unicode instead of utf8) 250 | - fix: [xls] support continue records 251 | - fix: [all] allow empty rows iterator 252 | 253 | ## 0.1.1 254 | - fix: remove some development `println!` 255 | 256 | ## 0.1.0 257 | - first release! 258 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Johann Tuffe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/README.md: -------------------------------------------------------------------------------- 1 | # calamine 2 | 3 | An Excel/OpenDocument Spreadsheets file reader/deserializer, in pure Rust. 4 | 5 | [![Build Status](https://travis-ci.org/tafia/calamine.svg?branch=master)](https://travis-ci.org/tafia/calamine) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/njpnhq54h5hxsgel/branch/master?svg=true)](https://ci.appveyor.com/project/tafia/calamine/branch/master) 7 | 8 | [Documentation](https://docs.rs/calamine/) 9 | 10 | ## Description 11 | 12 | **calamine** is a pure Rust library to read and deserialize any spreadsheet file: 13 | - excel like (`xls`, `xlsx`, `xlsm`, `xlsb`, `xla`, `xlam`) 14 | - opendocument spreadsheets (`ods`) 15 | 16 | As long as your files are *simple enough*, this library should just work. 17 | For anything else, please file an issue with a failing test or send a pull request! 18 | 19 | ## Examples 20 | 21 | ### Serde deserialization 22 | 23 | It is as simple as: 24 | 25 | ```rust 26 | use calamine::{open_workbook, Error, Xlsx, Reader, RangeDeserializerBuilder}; 27 | 28 | fn example() -> Result<(), Error> { 29 | let path = format!("{}/tests/temperature.xlsx", env!("CARGO_MANIFEST_DIR")); 30 | let mut workbook: Xlsx<_> = open_workbook(path)?; 31 | let range = workbook.worksheet_range("Sheet1") 32 | .ok_or(Error::Msg("Cannot find 'Sheet1'"))??; 33 | 34 | let mut iter = RangeDeserializerBuilder::new().from_range(&range)?; 35 | 36 | if let Some(result) = iter.next() { 37 | let (label, value): (String, f64) = result?; 38 | assert_eq!(label, "celsius"); 39 | assert_eq!(value, 22.2222); 40 | Ok(()) 41 | } else { 42 | Err(From::from("expected at least one record but got none")) 43 | } 44 | } 45 | ``` 46 | 47 | Note if you want to deserialise a column that may have invalid types (i.e. a float where some values may be strings), you can use Serde's `deserialize_with` field attribute: 48 | 49 | ```rust 50 | use serde::{Deserialize, Serialize}; 51 | use calamine::{RangeDeserializerBuilder, Reader, Xlsx}; 52 | 53 | 54 | #[derive(Serialize, Deserialize, Debug)] 55 | struct RawExcelRow { 56 | metric: String, 57 | #[serde(deserialize_with = "de_opt_f64")] 58 | value: Option, 59 | } 60 | 61 | 62 | // Convert value cell to Some(f64) if float or int, else None 63 | fn de_opt_f64<'de, D>(deserializer: D) -> Result, D::Error> 64 | where 65 | D: serde::Deserializer<'de>, 66 | { 67 | let data_type = calamine::DataType::deserialize(deserializer); 68 | match data_type { 69 | Ok(calamine::DataType::Error(_)) => Ok(None), 70 | Ok(calamine::DataType::Float(f)) => Ok(Some(f)), 71 | Ok(calamine::DataType::Int(i)) => Ok(Some(i as f64)), 72 | _ => Ok(None), 73 | } 74 | } 75 | 76 | fn main() -> Result<(), Box> { 77 | let path = format!("{}/tests/excel.xlsx", env!("CARGO_MANIFEST_DIR")); 78 | let mut excel: Xlsx<_> = open_workbook(path)?; 79 | 80 | let range = excel 81 | .worksheet_range("Sheet1") 82 | .ok_or(calamine::Error::Msg("Cannot find Sheet1"))??; 83 | 84 | let iter_result = 85 | RangeDeserializerBuilder::with_headers(&COLUMNS).from_range::<_, RawExcelRow>(&range)?; 86 | } 87 | ``` 88 | 89 | 90 | ### Reader: Simple 91 | 92 | ```rust 93 | use calamine::{Reader, Xlsx, open_workbook}; 94 | 95 | let mut excel: Xlsx<_> = open_workbook("file.xlsx").unwrap(); 96 | if let Some(Ok(r)) = excel.worksheet_range("Sheet1") { 97 | for row in r.rows() { 98 | println!("row={:?}, row[0]={:?}", row, row[0]); 99 | } 100 | } 101 | ``` 102 | 103 | ### Reader: More complex 104 | 105 | Let's assume 106 | - the file type (xls, xlsx ...) cannot be known at static time 107 | - we need to get all data from the workbook 108 | - we need to parse the vba 109 | - we need to see the defined names 110 | - and the formula! 111 | 112 | ```rust 113 | use calamine::{Reader, open_workbook_auto, Xlsx, DataType}; 114 | 115 | // opens a new workbook 116 | let path = ...; // we do not know the file type 117 | let mut workbook = open_workbook_auto(path).expect("Cannot open file"); 118 | 119 | // Read whole worksheet data and provide some statistics 120 | if let Some(Ok(range)) = workbook.worksheet_range("Sheet1") { 121 | let total_cells = range.get_size().0 * range.get_size().1; 122 | let non_empty_cells: usize = range.used_cells().count(); 123 | println!("Found {} cells in 'Sheet1', including {} non empty cells", 124 | total_cells, non_empty_cells); 125 | // alternatively, we can manually filter rows 126 | assert_eq!(non_empty_cells, range.rows() 127 | .flat_map(|r| r.iter().filter(|&c| c != &DataType::Empty)).count()); 128 | } 129 | 130 | // Check if the workbook has a vba project 131 | if let Some(Ok(mut vba)) = workbook.vba_project() { 132 | let vba = vba.to_mut(); 133 | let module1 = vba.get_module("Module 1").unwrap(); 134 | println!("Module 1 code:"); 135 | println!("{}", module1); 136 | for r in vba.get_references() { 137 | if r.is_missing() { 138 | println!("Reference {} is broken or not accessible", r.name); 139 | } 140 | } 141 | } 142 | 143 | // You can also get defined names definition (string representation only) 144 | for name in workbook.defined_names() { 145 | println!("name: {}, formula: {}", name.0, name.1); 146 | } 147 | 148 | // Now get all formula! 149 | let sheets = workbook.sheet_names().to_owned(); 150 | for s in sheets { 151 | println!("found {} formula in '{}'", 152 | workbook 153 | .worksheet_formula(&s) 154 | .expect("sheet not found") 155 | .expect("error while getting formula") 156 | .rows().flat_map(|r| r.iter().filter(|f| !f.is_empty())) 157 | .count(), 158 | s); 159 | } 160 | ``` 161 | 162 | ## Features 163 | 164 | - `dates`: Add date related fn to `DataType`. 165 | 166 | ### Others 167 | 168 | Browse the [examples](https://github.com/tafia/calamine/tree/master/examples) directory. 169 | 170 | ## Performance 171 | 172 | While there is no official benchmark yet, my first tests show a significant boost compared to official C# libraries: 173 | - Reading cell values: at least 3 times faster 174 | - Reading vba code: calamine does not read all sheets when opening your workbook, this is not fair 175 | 176 | ## Unsupported 177 | 178 | Many (most) part of the specifications are not implemented, the focus has been put on reading cell **values** and **vba** code. 179 | 180 | The main unsupported items are: 181 | - no support for writing excel files, this is a read-only library 182 | - no support for reading extra contents, such as formatting, excel parameter, encrypted components etc ... 183 | - no support for reading VB for opendocuments 184 | - dates: dates detection is not supported and will return `DataType::Float`. You can use the `dates` feature to get friendly conversions fn. 185 | 186 | ## Credits 187 | 188 | Thanks to [xlsx-js](https://github.com/SheetJS/js-xlsx) developers! 189 | This library is by far the simplest open source implementation I could find and helps making sense out of official documentation. 190 | 191 | Thanks also to all the contributors! 192 | 193 | ## License 194 | 195 | MIT 196 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - TARGET: x86_64-pc-windows-msvc 4 | 5 | install: 6 | - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% 7 | - curl -sSf -o rustup-init.exe https://win.rustup.rs/ 8 | - rustup-init.exe -y --default-host %TARGET% 9 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 10 | - rustc -V 11 | - cargo -V 12 | 13 | build: false 14 | 15 | test_script: 16 | - cargo build 17 | - cargo test --features dates 18 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/benches/basic.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use calamine::{open_workbook, Ods, Reader, Xls, Xlsb, Xlsx}; 6 | use std::fs::File; 7 | use std::io::BufReader; 8 | use test::Bencher; 9 | 10 | fn count>>(path: &str) -> usize { 11 | let path = format!("{}/{}", env!("CARGO_MANIFEST_DIR"), path); 12 | let mut excel: R = open_workbook(&path).expect("cannot open excel file"); 13 | 14 | let sheets = excel.sheet_names().to_owned(); 15 | let mut count = 0; 16 | for s in sheets { 17 | count += excel 18 | .worksheet_range(&s) 19 | .unwrap() 20 | .unwrap() 21 | .rows() 22 | .flat_map(|r| r.iter()) 23 | .count(); 24 | } 25 | count 26 | } 27 | 28 | #[bench] 29 | fn bench_xls(b: &mut Bencher) { 30 | b.iter(|| count::>("tests/issues.xls")); 31 | } 32 | 33 | #[bench] 34 | fn bench_xlsx(b: &mut Bencher) { 35 | b.iter(|| count::>("tests/issues.xlsx")); 36 | } 37 | 38 | #[bench] 39 | fn bench_xlsb(b: &mut Bencher) { 40 | b.iter(|| count::>("tests/issues.xlsb")); 41 | } 42 | 43 | #[bench] 44 | fn bench_ods(b: &mut Bencher) { 45 | b.iter(|| count::>("tests/issues.ods")); 46 | } 47 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/examples/excel_to_csv.rs: -------------------------------------------------------------------------------- 1 | use calamine::{open_workbook_auto, DataType, Range, Reader}; 2 | use std::env; 3 | use std::fs::File; 4 | use std::io::{BufWriter, Write}; 5 | use std::path::PathBuf; 6 | 7 | fn main() { 8 | // converts first argument into a csv (same name, silently overrides 9 | // if the file already exists 10 | 11 | let file = env::args() 12 | .nth(1) 13 | .expect("Please provide an excel file to convert"); 14 | let sheet = env::args() 15 | .nth(2) 16 | .expect("Expecting a sheet name as second argument"); 17 | 18 | let sce = PathBuf::from(file); 19 | match sce.extension().and_then(|s| s.to_str()) { 20 | Some("xlsx") | Some("xlsm") | Some("xlsb") | Some("xls") => (), 21 | _ => panic!("Expecting an excel file"), 22 | } 23 | 24 | let dest = sce.with_extension("csv"); 25 | let mut dest = BufWriter::new(File::create(dest).unwrap()); 26 | let mut xl = open_workbook_auto(&sce).unwrap(); 27 | let range = xl.worksheet_range(&sheet).unwrap().unwrap(); 28 | 29 | write_range(&mut dest, &range).unwrap(); 30 | } 31 | 32 | fn write_range(dest: &mut W, range: &Range) -> std::io::Result<()> { 33 | let n = range.get_size().1 - 1; 34 | for r in range.rows() { 35 | for (i, c) in r.iter().enumerate() { 36 | match *c { 37 | DataType::Empty => Ok(()), 38 | DataType::String(ref s) => write!(dest, "{}", s), 39 | DataType::Float(ref f) | DataType::DateTime(ref f) => write!(dest, "{}", f), 40 | DataType::Int(ref i) => write!(dest, "{}", i), 41 | DataType::Error(ref e) => write!(dest, "{:?}", e), 42 | DataType::Bool(ref b) => write!(dest, "{}", b), 43 | }?; 44 | if i != n { 45 | write!(dest, ";")?; 46 | } 47 | } 48 | write!(dest, "\r\n")?; 49 | } 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/examples/search_errors.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::{BufWriter, Write}; 4 | use std::path::PathBuf; 5 | 6 | use calamine::{open_workbook_auto, DataType, Error, Reader}; 7 | use glob::{glob, GlobError, GlobResult}; 8 | 9 | #[derive(Debug)] 10 | enum FileStatus { 11 | VbaError(Error), 12 | RangeError(Error), 13 | Glob(GlobError), 14 | } 15 | 16 | fn main() { 17 | // Search recursively for all excel files matching argument pattern 18 | // Output statistics: nb broken references, nb broken cells etc... 19 | let folder = env::args().nth(1).unwrap_or_else(|| ".".to_string()); 20 | let pattern = format!("{}/**/*.xl*", folder); 21 | let mut filecount = 0; 22 | 23 | let mut output = pattern 24 | .chars() 25 | .take_while(|c| *c != '*') 26 | .filter_map(|c| match c { 27 | ':' => None, 28 | '/' | '\\' | ' ' => Some('_'), 29 | c => Some(c), 30 | }) 31 | .collect::(); 32 | output.push_str("_errors.csv"); 33 | let mut output = BufWriter::new(File::create(output).unwrap()); 34 | 35 | for f in glob(&pattern).expect( 36 | "Failed to read excel glob,\ 37 | the first argument must correspond to a directory", 38 | ) { 39 | filecount += 1; 40 | match run(f) { 41 | Ok((f, missing, cell_errors)) => { 42 | writeln!(output, "{:?}~{:?}~{}", f, missing, cell_errors) 43 | } 44 | Err(e) => writeln!(output, "{:?}", e), 45 | } 46 | .unwrap_or_else(|e| println!("{:?}", e)) 47 | } 48 | 49 | println!("Found {} excel files", filecount); 50 | } 51 | 52 | fn run(f: GlobResult) -> Result<(PathBuf, Option, usize), FileStatus> { 53 | let f = f.map_err(FileStatus::Glob)?; 54 | 55 | println!("Analysing {:?}", f.display()); 56 | let mut xl = open_workbook_auto(&f).unwrap(); 57 | 58 | let mut missing = None; 59 | let mut cell_errors = 0; 60 | match xl.vba_project() { 61 | Some(Ok(vba)) => { 62 | missing = Some( 63 | vba.get_references() 64 | .iter() 65 | .filter(|r| r.is_missing()) 66 | .count(), 67 | ); 68 | } 69 | Some(Err(e)) => return Err(FileStatus::VbaError(e)), 70 | None => (), 71 | } 72 | 73 | // get owned sheet names 74 | let sheets = xl.sheet_names().to_owned(); 75 | 76 | for s in sheets { 77 | let range = xl 78 | .worksheet_range(&s) 79 | .unwrap() 80 | .map_err(FileStatus::RangeError)?; 81 | cell_errors += range 82 | .rows() 83 | .flat_map(|r| { 84 | r.iter().filter(|c| { 85 | if let DataType::Error(_) = **c { 86 | true 87 | } else { 88 | false 89 | } 90 | }) 91 | }) 92 | .count(); 93 | } 94 | 95 | Ok((f, missing, cell_errors)) 96 | } 97 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/auto.rs: -------------------------------------------------------------------------------- 1 | //! A module to convert file extension to reader 2 | 3 | use crate::errors::Error; 4 | use crate::vba::VbaProject; 5 | use crate::{open_workbook, DataType, Metadata, Ods, Range, Reader, Xls, Xlsb, Xlsx}; 6 | use std::borrow::Cow; 7 | use std::fs::File; 8 | use std::io::{BufReader, Cursor}; 9 | use std::path::Path; 10 | 11 | /// A wrapper over all sheets when the file type is not known at static time 12 | pub enum Sheets { 13 | /// Xls reader 14 | Xls(Xls>), 15 | /// Xlsx reader 16 | Xlsx(Xlsx>), 17 | 18 | /// Xls reader 19 | XlsBuff(Xls>>>), 20 | 21 | /// Xlsx reader 22 | XlsxBuff(Xlsx>>>), 23 | 24 | /// Xlsb reader 25 | Xlsb(Xlsb>), 26 | /// Ods reader 27 | Ods(Ods>), 28 | } 29 | 30 | /// Opens a workbook and define the file type at runtime. 31 | /// 32 | /// Whenever possible use the statically known `open_workbook` function instead 33 | pub fn open_workbook_auto>(path: P) -> Result { 34 | Ok(match path.as_ref().extension().and_then(|e| e.to_str()) { 35 | Some("xls") | Some("xla") => Sheets::Xls(open_workbook(&path).map_err(Error::Xls)?), 36 | Some("xlsx") | Some("xlsm") | Some("xlam") => { 37 | Sheets::Xlsx(open_workbook(&path).map_err(Error::Xlsx)?) 38 | } 39 | Some("xlsb") => Sheets::Xlsb(open_workbook(&path).map_err(Error::Xlsb)?), 40 | Some("ods") => Sheets::Ods(open_workbook(&path).map_err(Error::Ods)?), 41 | _ => { 42 | fn open_workbook_xlsx>(path: P) -> Result { 43 | Ok(Sheets::Xlsx(open_workbook(&path).map_err(Error::Xlsx)?)) 44 | } 45 | fn open_workbook_xls>(path: P) -> Result { 46 | Ok(Sheets::Xls(open_workbook(&path).map_err(Error::Xls)?)) 47 | } 48 | fn open_workbook_xlsb>(path: P) -> Result { 49 | Ok(Sheets::Xlsb(open_workbook(&path).map_err(Error::Xlsb)?)) 50 | } 51 | fn open_workbook_ods>(path: P) -> Result { 52 | Ok(Sheets::Ods(open_workbook(&path).map_err(Error::Ods)?)) 53 | } 54 | 55 | return if let Ok(ret) = open_workbook_xlsx(&path) { 56 | Ok(ret) 57 | } else if let Ok(ret) = open_workbook_xls(&path) { 58 | Ok(ret) 59 | } else if let Ok(ret) = open_workbook_xlsb(&path) { 60 | Ok(ret) 61 | } else if let Ok(ret) = open_workbook_ods(&path) { 62 | Ok(ret) 63 | } else { 64 | Err(Error::Msg("Cannot detect file format")) 65 | }; 66 | } 67 | }) 68 | } 69 | 70 | impl Reader for Sheets { 71 | type RS = BufReader; 72 | type Error = Error; 73 | 74 | /// Creates a new instance. 75 | fn new(_reader: Self::RS) -> Result { 76 | Err(Error::Msg("Sheets must be created from a Path")) 77 | } 78 | 79 | /// Gets `VbaProject` 80 | fn vba_project(&mut self) -> Option, Self::Error>> { 81 | match *self { 82 | Sheets::XlsBuff(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Xls)), 83 | Sheets::XlsxBuff(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Xlsx)), 84 | 85 | Sheets::Xls(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Xls)), 86 | Sheets::Xlsx(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Xlsx)), 87 | Sheets::Xlsb(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Xlsb)), 88 | Sheets::Ods(ref mut e) => e.vba_project().map(|vba| vba.map_err(Error::Ods)), 89 | } 90 | } 91 | 92 | /// Initialize 93 | fn metadata(&self) -> &Metadata { 94 | match *self { 95 | Sheets::XlsBuff(ref e) => e.metadata(), 96 | Sheets::XlsxBuff(ref e) => e.metadata(), 97 | 98 | Sheets::Xls(ref e) => e.metadata(), 99 | Sheets::Xlsx(ref e) => e.metadata(), 100 | Sheets::Xlsb(ref e) => e.metadata(), 101 | Sheets::Ods(ref e) => e.metadata(), 102 | } 103 | } 104 | 105 | /// Read worksheet data in corresponding worksheet path 106 | fn worksheet_range(&mut self, name: &str) -> Option, Self::Error>> { 107 | match *self { 108 | Sheets::XlsBuff(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Xls)), 109 | Sheets::XlsxBuff(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Xlsx)), 110 | 111 | Sheets::Xls(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Xls)), 112 | Sheets::Xlsx(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Xlsx)), 113 | Sheets::Xlsb(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Xlsb)), 114 | Sheets::Ods(ref mut e) => e.worksheet_range(name).map(|r| r.map_err(Error::Ods)), 115 | } 116 | } 117 | 118 | /// Read worksheet formula in corresponding worksheet path 119 | fn worksheet_formula(&mut self, name: &str) -> Option, Self::Error>> { 120 | match *self { 121 | Sheets::XlsBuff(ref mut e) => e.worksheet_formula(name).map(|r| r.map_err(Error::Xls)), 122 | Sheets::XlsxBuff(ref mut e) => { 123 | e.worksheet_formula(name).map(|r| r.map_err(Error::Xlsx)) 124 | } 125 | 126 | Sheets::Xls(ref mut e) => e.worksheet_formula(name).map(|r| r.map_err(Error::Xls)), 127 | Sheets::Xlsx(ref mut e) => e.worksheet_formula(name).map(|r| r.map_err(Error::Xlsx)), 128 | Sheets::Xlsb(ref mut e) => e.worksheet_formula(name).map(|r| r.map_err(Error::Xlsb)), 129 | Sheets::Ods(ref mut e) => e.worksheet_formula(name).map(|r| r.map_err(Error::Ods)), 130 | } 131 | } 132 | 133 | fn worksheets(&mut self) -> Vec<(String, Range)> { 134 | match *self { 135 | Sheets::XlsBuff(ref mut e) => e.worksheets(), 136 | Sheets::XlsxBuff(ref mut e) => e.worksheets(), 137 | 138 | Sheets::Xls(ref mut e) => e.worksheets(), 139 | Sheets::Xlsx(ref mut e) => e.worksheets(), 140 | Sheets::Xlsb(ref mut e) => e.worksheets(), 141 | Sheets::Ods(ref mut e) => e.worksheets(), 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/cfb.rs: -------------------------------------------------------------------------------- 1 | //! Coumpound File Binary format MS-CFB 2 | 3 | use std::borrow::Cow; 4 | use std::cmp::min; 5 | use std::convert::TryInto; 6 | use std::io::Read; 7 | 8 | use log::debug; 9 | 10 | use encoding_rs::{Encoding, UTF_16LE, UTF_8}; 11 | 12 | use crate::utils::*; 13 | 14 | const RESERVED_SECTORS: u32 = 0xFFFF_FFFA; 15 | const DIFSECT: u32 = 0xFFFF_FFFC; 16 | // const FATSECT: u32 = 0xFFFF_FFFD; 17 | const ENDOFCHAIN: u32 = 0xFFFF_FFFE; 18 | //const FREESECT: u32 = 0xFFFF_FFFF; 19 | 20 | /// A Cfb specific error enum 21 | #[derive(Debug)] 22 | pub enum CfbError { 23 | Io(std::io::Error), 24 | 25 | Ole, 26 | EmptyRootDir, 27 | StreamNotFound(String), 28 | Invalid { 29 | name: &'static str, 30 | expected: &'static str, 31 | found: u16, 32 | }, 33 | CodePageNotFound(u16), 34 | } 35 | 36 | impl std::fmt::Display for CfbError { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | match self { 39 | CfbError::Io(e) => write!(f, "I/O error: {}", e), 40 | CfbError::Ole => write!(f, "Invalid OLE signature (not an office document?)"), 41 | CfbError::EmptyRootDir => write!(f, "Empty Root directory"), 42 | CfbError::StreamNotFound(e) => write!(f, "Cannot find {} stream", e), 43 | CfbError::Invalid { 44 | name, 45 | expected, 46 | found, 47 | } => write!( 48 | f, 49 | "Invalid {}, expecting {} found {:X}", 50 | name, expected, found 51 | ), 52 | CfbError::CodePageNotFound(e) => write!(f, "Codepage {:X} not found", e), 53 | } 54 | } 55 | } 56 | 57 | impl std::error::Error for CfbError { 58 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 59 | match self { 60 | CfbError::Io(e) => Some(e), 61 | _ => None, 62 | } 63 | } 64 | } 65 | 66 | /// A struct for managing Compound File Binary format 67 | #[derive(Debug, Clone)] 68 | pub struct Cfb { 69 | directories: Vec, 70 | sectors: Sectors, 71 | fats: Vec, 72 | mini_sectors: Sectors, 73 | mini_fats: Vec, 74 | } 75 | 76 | impl Cfb { 77 | /// Create a new `Cfb` 78 | /// 79 | /// Starts reading project metadata (header, directories, sectors and minisectors). 80 | pub fn new(mut reader: &mut R, len: usize) -> Result { 81 | // load header 82 | let (h, mut difat) = Header::from_reader(&mut reader)?; 83 | let mut sectors = Sectors::new(h.sector_size, Vec::with_capacity(len)); 84 | 85 | // load fat and dif sectors 86 | debug!("load difat"); 87 | let mut sector_id = h.difat_start; 88 | while sector_id < RESERVED_SECTORS { 89 | difat.extend(to_u32(sectors.get(sector_id, reader)?)); 90 | sector_id = difat.pop().unwrap(); //TODO: check if in infinite loop 91 | } 92 | 93 | // load the FATs 94 | debug!("load fat (len {})", h.fat_len); 95 | let mut fats = Vec::with_capacity(h.fat_len); 96 | for id in difat.into_iter().filter(|id| *id < DIFSECT) { 97 | fats.extend(to_u32(sectors.get(id, reader)?)); 98 | } 99 | 100 | // get the list of directory sectors 101 | debug!("load directories"); 102 | let dirs = sectors.get_chain(h.dir_start, &fats, reader, h.dir_len * h.sector_size)?; 103 | let dirs = dirs 104 | .chunks(128) 105 | .map(|c| Directory::from_slice(c, h.sector_size)) 106 | .collect::>(); 107 | 108 | if dirs.is_empty() || (h.version != 3 && dirs[0].start == ENDOFCHAIN) { 109 | return Err(CfbError::EmptyRootDir); 110 | } 111 | debug!("{:?}", dirs); 112 | 113 | // load the mini streams 114 | debug!("load minis"); 115 | let ministream = sectors.get_chain(dirs[0].start, &fats, reader, dirs[0].len)?; 116 | let minifat = sectors.get_chain( 117 | h.mini_fat_start, 118 | &fats, 119 | reader, 120 | h.mini_fat_len * h.sector_size, 121 | )?; 122 | let minifat = to_u32(&minifat).collect(); 123 | Ok(Cfb { 124 | directories: dirs, 125 | sectors, 126 | fats, 127 | mini_sectors: Sectors::new(64, ministream), 128 | mini_fats: minifat, 129 | }) 130 | } 131 | 132 | /// Checks if directory exists 133 | pub fn has_directory(&self, name: &str) -> bool { 134 | self.directories.iter().any(|d| &*d.name == name) 135 | } 136 | 137 | /// Gets a stream by name out of directories 138 | pub fn get_stream(&mut self, name: &str, r: &mut R) -> Result, CfbError> { 139 | match self.directories.iter().find(|d| &*d.name == name) { 140 | None => Err(CfbError::StreamNotFound(name.to_string())), 141 | Some(d) => { 142 | if d.len < 4096 { 143 | // TODO: Study the possibility to return a `VecArray` (stack allocated) 144 | self.mini_sectors 145 | .get_chain(d.start, &self.mini_fats, r, d.len) 146 | } else { 147 | self.sectors.get_chain(d.start, &self.fats, r, d.len) 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | /// A hidden struct which defines cfb files structure 155 | #[derive(Debug)] 156 | struct Header { 157 | version: u16, 158 | sector_size: usize, 159 | dir_len: usize, 160 | dir_start: u32, 161 | fat_len: usize, 162 | mini_fat_len: usize, 163 | mini_fat_start: u32, 164 | difat_start: u32, 165 | } 166 | 167 | impl Header { 168 | fn from_reader(f: &mut R) -> Result<(Header, Vec), CfbError> { 169 | let mut buf = [0u8; 512]; 170 | f.read_exact(&mut buf).map_err(CfbError::Io)?; 171 | 172 | // check ole signature 173 | let signature = buf 174 | .get(0..8) 175 | .map(|slice| u64::from_le_bytes(slice.try_into().unwrap())); 176 | if signature != Some(0xE11A_B1A1_E011_CFD0) { 177 | return Err(CfbError::Ole); 178 | } 179 | 180 | let version = read_u16(&buf[26..28]); 181 | 182 | let sector_size = match read_u16(&buf[30..32]) { 183 | 0x0009 => 512, 184 | 0x000C => { 185 | // sector size is 4096 bytes, but header is 512 bytes, 186 | // so the remaining sector bytes have to be read 187 | let mut buf_end = [0u8; 3584]; 188 | f.read_exact(&mut buf_end).map_err(CfbError::Io)?; 189 | 4096 190 | } 191 | s => { 192 | return Err(CfbError::Invalid { 193 | name: "sector shift", 194 | expected: "0x09 or 0x0C", 195 | found: s, 196 | }); 197 | } 198 | }; 199 | 200 | if read_u16(&buf[32..34]) != 0x0006 { 201 | return Err(CfbError::Invalid { 202 | name: "minisector shift", 203 | expected: "0x06", 204 | found: read_u16(&buf[32..34]), 205 | }); 206 | } 207 | 208 | let dir_len = read_usize(&buf[40..44]); 209 | let fat_len = read_usize(&buf[44..48]); 210 | let dir_start = read_u32(&buf[48..52]); 211 | let mini_fat_start = read_u32(&buf[60..64]); 212 | let mini_fat_len = read_usize(&buf[64..68]); 213 | let difat_start = read_u32(&buf[68..72]); 214 | let difat_len = read_usize(&buf[62..76]); 215 | 216 | let mut difat = Vec::with_capacity(difat_len); 217 | difat.extend(to_u32(&buf[76..512])); 218 | 219 | Ok(( 220 | Header { 221 | version, 222 | sector_size, 223 | dir_len, 224 | fat_len, 225 | dir_start, 226 | mini_fat_len, 227 | mini_fat_start, 228 | difat_start, 229 | }, 230 | difat, 231 | )) 232 | } 233 | } 234 | 235 | /// A struct corresponding to the elementary block of memory 236 | /// 237 | /// `data` will persist in memory to ensure the file is read once 238 | #[derive(Debug, Clone)] 239 | struct Sectors { 240 | data: Vec, 241 | size: usize, 242 | } 243 | 244 | impl Sectors { 245 | fn new(size: usize, data: Vec) -> Sectors { 246 | Sectors { data, size } 247 | } 248 | 249 | fn get(&mut self, id: u32, r: &mut R) -> Result<&[u8], CfbError> { 250 | let start = id as usize * self.size; 251 | let end = start + self.size; 252 | if end > self.data.len() { 253 | let mut len = self.data.len(); 254 | self.data.resize(end, 0); 255 | // read_exact or stop if EOF 256 | while len < end { 257 | let read = r.read(&mut self.data[len..end]).map_err(CfbError::Io)?; 258 | if read == 0 { 259 | return Ok(&self.data[start..len]); 260 | } 261 | len += read; 262 | } 263 | } 264 | Ok(&self.data[start..end]) 265 | } 266 | 267 | fn get_chain( 268 | &mut self, 269 | mut sector_id: u32, 270 | fats: &[u32], 271 | r: &mut R, 272 | len: usize, 273 | ) -> Result, CfbError> { 274 | let mut chain = if len > 0 { 275 | Vec::with_capacity(len) 276 | } else { 277 | Vec::new() 278 | }; 279 | while sector_id != ENDOFCHAIN { 280 | chain.extend_from_slice(self.get(sector_id, r)?); 281 | sector_id = fats[sector_id as usize]; 282 | } 283 | if len > 0 { 284 | chain.truncate(len); 285 | } 286 | Ok(chain) 287 | } 288 | } 289 | 290 | /// A struct representing sector organizations, behaves similarly to a tree 291 | #[derive(Debug, Clone)] 292 | struct Directory { 293 | name: String, 294 | start: u32, 295 | len: usize, 296 | } 297 | 298 | impl Directory { 299 | fn from_slice(buf: &[u8], sector_size: usize) -> Directory { 300 | let mut name = UTF_16LE.decode(&buf[..64]).0.into_owned(); 301 | if let Some(l) = name.as_bytes().iter().position(|b| *b == 0) { 302 | name.truncate(l); 303 | } 304 | let start = read_u32(&buf[116..120]); 305 | let len: usize = if sector_size == 512 { 306 | read_u32(&buf[120..124]).try_into().unwrap() 307 | } else { 308 | read_u64(&buf[120..128]).try_into().unwrap() 309 | }; 310 | 311 | Directory { start, len, name } 312 | } 313 | } 314 | 315 | /// Decompresses stream 316 | pub fn decompress_stream(s: &[u8]) -> Result, CfbError> { 317 | const POWER_2: [usize; 16] = [ 318 | 1, 319 | 1 << 1, 320 | 1 << 2, 321 | 1 << 3, 322 | 1 << 4, 323 | 1 << 5, 324 | 1 << 6, 325 | 1 << 7, 326 | 1 << 8, 327 | 1 << 9, 328 | 1 << 10, 329 | 1 << 11, 330 | 1 << 12, 331 | 1 << 13, 332 | 1 << 14, 333 | 1 << 15, 334 | ]; 335 | 336 | debug!("decompress stream"); 337 | let mut res = Vec::new(); 338 | 339 | if s[0] != 0x01 { 340 | return Err(CfbError::Invalid { 341 | name: "signature", 342 | expected: "0x01", 343 | found: s[0] as u16, 344 | }); 345 | } 346 | 347 | let mut i = 1; 348 | while i < s.len() { 349 | let chunk_header = read_u16(&s[i..]); 350 | i += 2; 351 | 352 | // each 'chunk' is 4096 wide, let's reserve that space 353 | let start = res.len(); 354 | res.reserve(4096); 355 | 356 | let chunk_size = chunk_header & 0x0FFF; 357 | let chunk_signature = (chunk_header & 0x7000) >> 12; 358 | let chunk_flag = (chunk_header & 0x8000) >> 15; 359 | 360 | assert_eq!(chunk_signature, 0b011, "i={}, len={}", i, s.len()); 361 | 362 | if chunk_flag == 0 { 363 | // uncompressed 364 | res.extend_from_slice(&s[i..i + 4096]); 365 | i += 4096; 366 | } else { 367 | let mut chunk_len = 0; 368 | let mut buf = [0u8; 4096]; 369 | 'chunk: loop { 370 | if i >= s.len() { 371 | break; 372 | } 373 | 374 | let bit_flags = s[i]; 375 | i += 1; 376 | chunk_len += 1; 377 | 378 | for bit_index in 0..8 { 379 | if chunk_len > chunk_size { 380 | break 'chunk; 381 | } 382 | 383 | if (bit_flags & (1 << bit_index)) == 0 { 384 | // literal token 385 | res.push(s[i]); 386 | i += 1; 387 | chunk_len += 1; 388 | } else { 389 | // copy token 390 | let token = read_u16(&s[i..]); 391 | i += 2; 392 | chunk_len += 2; 393 | 394 | let decomp_len = res.len() - start; 395 | let bit_count = (4..16).find(|i| POWER_2[*i] >= decomp_len).unwrap(); 396 | let len_mask = 0xFFFF >> bit_count; 397 | let mut len = (token & len_mask) as usize + 3; 398 | let offset = ((token & !len_mask) >> (16 - bit_count)) as usize + 1; 399 | 400 | while len > offset { 401 | buf[..offset].copy_from_slice(&res[res.len() - offset..]); 402 | res.extend_from_slice(&buf[..offset]); 403 | len -= offset; 404 | } 405 | buf[..len] 406 | .copy_from_slice(&res[res.len() - offset..res.len() - offset + len]); 407 | res.extend_from_slice(&buf[..len]); 408 | } 409 | } 410 | } 411 | } 412 | } 413 | Ok(res) 414 | } 415 | 416 | #[derive(Clone)] 417 | pub struct XlsEncoding { 418 | encoding: &'static Encoding, 419 | } 420 | 421 | impl XlsEncoding { 422 | pub fn from_codepage(codepage: u16) -> Result { 423 | let e = 424 | codepage::to_encoding(codepage).ok_or_else(|| CfbError::CodePageNotFound(codepage))?; 425 | Ok(XlsEncoding { encoding: e }) 426 | } 427 | 428 | fn high_byte(&self, high_byte: Option) -> Option { 429 | high_byte.or_else(|| { 430 | if self.encoding == UTF_8 || self.encoding.is_single_byte() { 431 | None 432 | } else { 433 | Some(false) 434 | } 435 | }) 436 | } 437 | 438 | pub fn decode_to( 439 | &self, 440 | stream: &[u8], 441 | len: usize, 442 | s: &mut String, 443 | high_byte: Option, 444 | ) -> (usize, usize) { 445 | let (l, ub, bytes) = match self.high_byte(high_byte) { 446 | None => { 447 | let l = min(stream.len(), len); 448 | (l, l, Cow::Borrowed(&stream[..l])) 449 | } 450 | Some(false) => { 451 | let l = min(stream.len(), len); 452 | 453 | // add 0x00 high bytes to unicodes 454 | let mut bytes = vec![0; l * 2]; 455 | for (i, sce) in stream.iter().take(l).enumerate() { 456 | bytes[2 * i] = *sce; 457 | } 458 | (l, l, Cow::Owned(bytes)) 459 | } 460 | Some(true) => { 461 | let l = min(stream.len() / 2, len); 462 | (l, 2 * l, Cow::Borrowed(&stream[..2 * l])) 463 | } 464 | }; 465 | 466 | s.push_str(&self.encoding.decode(&bytes).0); 467 | (l, ub) 468 | } 469 | 470 | pub fn decode_all(&self, stream: &[u8], high_byte: Option) -> String { 471 | let mut s = String::with_capacity(stream.len()); 472 | let _ = self.decode_to(stream, stream.len(), &mut s, high_byte); 473 | s 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/datatype.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::Visitor; 4 | use serde::{self, Deserialize}; 5 | 6 | use super::CellErrorType; 7 | 8 | /// An enum to represent all different data types that can appear as 9 | /// a value in a worksheet cell 10 | #[derive(Debug, Clone, PartialEq)] 11 | pub enum DataType { 12 | /// Unsigned integer 13 | Int(i64), 14 | /// Float 15 | Float(f64), 16 | /// String 17 | String(String), 18 | /// Boolean 19 | Bool(bool), 20 | /// Date or Time 21 | DateTime(f64), 22 | /// Error 23 | Error(CellErrorType), 24 | /// Empty cell 25 | Empty, 26 | } 27 | 28 | impl Default for DataType { 29 | fn default() -> DataType { 30 | DataType::Empty 31 | } 32 | } 33 | 34 | impl DataType { 35 | /// Assess if datatype is empty 36 | pub fn is_empty(&self) -> bool { 37 | *self == DataType::Empty 38 | } 39 | /// Assess if datatype is a int 40 | pub fn is_int(&self) -> bool { 41 | if let DataType::Int(_) = *self { 42 | true 43 | } else { 44 | false 45 | } 46 | } 47 | /// Assess if datatype is a float 48 | pub fn is_float(&self) -> bool { 49 | if let DataType::Float(_) = *self { 50 | true 51 | } else { 52 | false 53 | } 54 | } 55 | /// Assess if datatype is a bool 56 | pub fn is_bool(&self) -> bool { 57 | if let DataType::Bool(_) = *self { 58 | true 59 | } else { 60 | false 61 | } 62 | } 63 | /// Assess if datatype is a string 64 | pub fn is_string(&self) -> bool { 65 | if let DataType::String(_) = *self { 66 | true 67 | } else { 68 | false 69 | } 70 | } 71 | 72 | /// Try getting int value 73 | pub fn get_int(&self) -> Option { 74 | if let DataType::Int(v) = self { 75 | Some(*v) 76 | } else { 77 | None 78 | } 79 | } 80 | /// Try getting float value 81 | pub fn get_float(&self) -> Option { 82 | if let DataType::Float(v) = self { 83 | Some(*v) 84 | } else { 85 | None 86 | } 87 | } 88 | /// Try getting bool value 89 | pub fn get_bool(&self) -> Option { 90 | if let DataType::Bool(v) = self { 91 | Some(*v) 92 | } else { 93 | None 94 | } 95 | } 96 | /// Try getting string value 97 | pub fn get_string(&self) -> Option<&str> { 98 | if let DataType::String(v) = self { 99 | Some(&**v) 100 | } else { 101 | None 102 | } 103 | } 104 | 105 | /// Try converting data type into a date 106 | #[cfg(feature = "dates")] 107 | pub fn as_date(&self) -> Option { 108 | self.as_datetime().map(|dt| dt.date()) 109 | } 110 | 111 | /// Try converting data type into a time 112 | #[cfg(feature = "dates")] 113 | pub fn as_time(&self) -> Option { 114 | self.as_datetime().map(|dt| dt.time()) 115 | } 116 | 117 | /// Try converting data type into a datetime 118 | #[cfg(feature = "dates")] 119 | pub fn as_datetime(&self) -> Option { 120 | match self { 121 | DataType::Int(x) => { 122 | let days = x - 25569; 123 | let secs = days * 86400; 124 | chrono::NaiveDateTime::from_timestamp_opt(secs, 0) 125 | } 126 | DataType::Float(f) | DataType::DateTime(f) => { 127 | let unix_days = f - 25569.; 128 | let unix_secs = unix_days * 86400.; 129 | let secs = unix_secs.trunc() as i64; 130 | let nsecs = (unix_secs.fract().abs() * 1e9) as u32; 131 | chrono::NaiveDateTime::from_timestamp_opt(secs, nsecs) 132 | } 133 | _ => None, 134 | } 135 | } 136 | } 137 | 138 | impl PartialEq for DataType { 139 | fn eq(&self, other: &str) -> bool { 140 | match *self { 141 | DataType::String(ref s) if s == other => true, 142 | _ => false, 143 | } 144 | } 145 | } 146 | 147 | impl PartialEq for DataType { 148 | fn eq(&self, other: &f64) -> bool { 149 | match *self { 150 | DataType::Float(ref s) if *s == *other => true, 151 | _ => false, 152 | } 153 | } 154 | } 155 | 156 | impl PartialEq for DataType { 157 | fn eq(&self, other: &bool) -> bool { 158 | match *self { 159 | DataType::Bool(ref s) if *s == *other => true, 160 | _ => false, 161 | } 162 | } 163 | } 164 | 165 | impl PartialEq for DataType { 166 | fn eq(&self, other: &i64) -> bool { 167 | match *self { 168 | DataType::Int(ref s) if *s == *other => true, 169 | _ => false, 170 | } 171 | } 172 | } 173 | 174 | impl fmt::Display for DataType { 175 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { 176 | match *self { 177 | DataType::Int(ref e) => write!(f, "{}", e), 178 | DataType::Float(ref e) => write!(f, "{}", e), 179 | DataType::String(ref e) => write!(f, "{}", e), 180 | DataType::Bool(ref e) => write!(f, "{}", e), 181 | DataType::DateTime(ref e) => write!(f, "{}", e), 182 | DataType::Error(ref e) => write!(f, "{}", e), 183 | DataType::Empty => Ok(()), 184 | } 185 | } 186 | } 187 | 188 | impl<'de> Deserialize<'de> for DataType { 189 | #[inline] 190 | fn deserialize(deserializer: D) -> Result 191 | where 192 | D: serde::Deserializer<'de>, 193 | { 194 | struct DataTypeVisitor; 195 | 196 | impl<'de> Visitor<'de> for DataTypeVisitor { 197 | type Value = DataType; 198 | 199 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 200 | formatter.write_str("any valid JSON value") 201 | } 202 | 203 | #[inline] 204 | fn visit_bool(self, value: bool) -> Result { 205 | Ok(DataType::Bool(value)) 206 | } 207 | 208 | #[inline] 209 | fn visit_i64(self, value: i64) -> Result { 210 | Ok(DataType::Int(value)) 211 | } 212 | 213 | #[inline] 214 | fn visit_u64(self, value: u64) -> Result { 215 | Ok(DataType::Int(value as i64)) 216 | } 217 | 218 | #[inline] 219 | fn visit_f64(self, value: f64) -> Result { 220 | Ok(DataType::Float(value)) 221 | } 222 | 223 | #[inline] 224 | fn visit_str(self, value: &str) -> Result 225 | where 226 | E: serde::de::Error, 227 | { 228 | self.visit_string(String::from(value)) 229 | } 230 | 231 | #[inline] 232 | fn visit_string(self, value: String) -> Result { 233 | Ok(DataType::String(value)) 234 | } 235 | 236 | #[inline] 237 | fn visit_none(self) -> Result { 238 | Ok(DataType::Empty) 239 | } 240 | 241 | #[inline] 242 | fn visit_some(self, deserializer: D) -> Result 243 | where 244 | D: serde::Deserializer<'de>, 245 | { 246 | Deserialize::deserialize(deserializer) 247 | } 248 | 249 | #[inline] 250 | fn visit_unit(self) -> Result { 251 | Ok(DataType::Empty) 252 | } 253 | } 254 | 255 | deserializer.deserialize_any(DataTypeVisitor) 256 | } 257 | } 258 | 259 | macro_rules! define_from { 260 | ($variant:path, $ty:ty) => { 261 | impl From<$ty> for DataType { 262 | fn from(v: $ty) -> Self { 263 | $variant(v) 264 | } 265 | } 266 | }; 267 | } 268 | 269 | define_from!(DataType::Int, i64); 270 | define_from!(DataType::Float, f64); 271 | define_from!(DataType::String, String); 272 | define_from!(DataType::Bool, bool); 273 | define_from!(DataType::Error, CellErrorType); 274 | 275 | impl<'a> From<&'a str> for DataType { 276 | fn from(v: &'a str) -> Self { 277 | DataType::String(String::from(v)) 278 | } 279 | } 280 | 281 | impl From<()> for DataType { 282 | fn from(_: ()) -> Self { 283 | DataType::Empty 284 | } 285 | } 286 | 287 | impl From> for DataType 288 | where 289 | DataType: From, 290 | { 291 | fn from(v: Option) -> Self { 292 | match v { 293 | Some(v) => From::from(v), 294 | None => DataType::Empty, 295 | } 296 | } 297 | } 298 | 299 | #[cfg(all(test, feature = "dates"))] 300 | mod tests { 301 | use super::*; 302 | 303 | #[test] 304 | fn test_dates() { 305 | use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime}; 306 | 307 | let unix_epoch = DataType::Float(25569.); 308 | assert_eq!( 309 | unix_epoch.as_datetime(), 310 | Some(NaiveDateTime::new( 311 | NaiveDate::from_ymd(1970, 1, 1), 312 | NaiveTime::from_hms(0, 0, 0) 313 | )) 314 | ); 315 | 316 | let unix_epoch_15h30m = DataType::Float(25569.645833333333333); 317 | let chrono_dt = NaiveDateTime::new( 318 | NaiveDate::from_ymd(1970, 1, 1), 319 | NaiveTime::from_hms(15, 30, 0), 320 | ); 321 | let micro = Duration::microseconds(1); 322 | assert!(unix_epoch_15h30m.as_datetime().unwrap() - chrono_dt < micro); 323 | } 324 | 325 | #[test] 326 | fn test_int_dates() { 327 | use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime}; 328 | 329 | let unix_epoch = DataType::Int(25569); 330 | assert_eq!( 331 | unix_epoch.as_datetime(), 332 | Some(NaiveDateTime::new( 333 | NaiveDate::from_ymd(1970, 1, 1), 334 | NaiveTime::from_hms(0, 0, 0) 335 | )) 336 | ); 337 | 338 | let time = DataType::Int(44060); 339 | assert_eq!( 340 | time.as_datetime(), 341 | Some(NaiveDateTime::new( 342 | NaiveDate::from_ymd(2020, 8, 17), 343 | NaiveTime::from_hms(0, 0, 0), 344 | )) 345 | ); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/de.rs: -------------------------------------------------------------------------------- 1 | use serde::de::value::BorrowedStrDeserializer; 2 | use serde::de::{self, DeserializeOwned, DeserializeSeed, SeqAccess, Visitor}; 3 | use serde::{self, forward_to_deserialize_any, Deserialize}; 4 | use std::marker::PhantomData; 5 | use std::{fmt, slice, str}; 6 | 7 | use super::{CellErrorType, CellType, DataType, Range, Rows}; 8 | 9 | /// A cell deserialization specific error enum 10 | #[derive(Debug)] 11 | pub enum DeError { 12 | /// Cell out of range 13 | CellOutOfRange { 14 | /// Position tried 15 | try_pos: (u32, u32), 16 | /// Minimum position 17 | min_pos: (u32, u32), 18 | }, 19 | /// The cell value is an error 20 | CellError { 21 | /// Cell value error 22 | err: CellErrorType, 23 | /// Cell position 24 | pos: (u32, u32), 25 | }, 26 | /// Unexpected end of row 27 | UnexpectedEndOfRow { 28 | /// Cell position 29 | pos: (u32, u32), 30 | }, 31 | /// Required header not found 32 | HeaderNotFound(String), 33 | /// Serde specific error 34 | Custom(String), 35 | } 36 | 37 | impl fmt::Display for DeError { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 39 | match *self { 40 | DeError::CellOutOfRange { 41 | ref try_pos, 42 | ref min_pos, 43 | } => write!( 44 | f, 45 | "there is no cell at position '{:?}'.Minimum position is '{:?}'", 46 | try_pos, min_pos 47 | ), 48 | DeError::CellError { ref pos, ref err } => { 49 | write!(f, "Cell error at position '{:?}': {}", pos, err) 50 | } 51 | DeError::UnexpectedEndOfRow { ref pos } => { 52 | write!(f, "Unexpected end of row at position '{:?}'", pos) 53 | } 54 | DeError::HeaderNotFound(ref header) => { 55 | write!(f, "Cannot find header named '{}'", header) 56 | } 57 | DeError::Custom(ref s) => write!(f, "{}", s), 58 | } 59 | } 60 | } 61 | 62 | impl std::error::Error for DeError { 63 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 64 | None 65 | } 66 | } 67 | 68 | impl de::Error for DeError { 69 | fn custom(msg: T) -> Self { 70 | DeError::Custom(msg.to_string()) 71 | } 72 | } 73 | 74 | #[derive(Clone)] 75 | pub enum Headers<'h, H> { 76 | None, 77 | All, 78 | Custom(&'h [H]), 79 | } 80 | 81 | /// Builds a `Range` deserializer with some configuration options. 82 | /// 83 | /// This can be used to optionally parse the first row as a header. Once built, 84 | /// a `RangeDeserializer`s cannot be changed. 85 | #[derive(Clone)] 86 | pub struct RangeDeserializerBuilder<'h, H> { 87 | headers: Headers<'h, H>, 88 | } 89 | 90 | impl Default for RangeDeserializerBuilder<'static, &'static str> { 91 | fn default() -> Self { 92 | RangeDeserializerBuilder { 93 | headers: Headers::All, 94 | } 95 | } 96 | } 97 | 98 | impl RangeDeserializerBuilder<'static, &'static str> { 99 | /// Constructs a new builder for configuring `Range` deserialization. 100 | pub fn new() -> Self { 101 | Default::default() 102 | } 103 | 104 | /// Decide whether to treat the first row as a special header row. 105 | /// 106 | /// # Example 107 | /// 108 | /// ``` 109 | /// # use calamine::{DataType, Error, open_workbook, Xlsx, Reader, RangeDeserializerBuilder}; 110 | /// fn main() -> Result<(), Error> { 111 | /// let path = format!("{}/tests/temperature.xlsx", env!("CARGO_MANIFEST_DIR")); 112 | /// let mut workbook: Xlsx<_> = open_workbook(path)?; 113 | /// let range = workbook.worksheet_range("Sheet1") 114 | /// .ok_or(Error::Msg("Cannot find 'Sheet1'"))??; 115 | /// 116 | /// let mut iter = RangeDeserializerBuilder::new() 117 | /// .has_headers(false) 118 | /// .from_range(&range)?; 119 | /// 120 | /// if let Some(result) = iter.next() { 121 | /// let row: Vec = result?; 122 | /// assert_eq!(row, [DataType::from("label"), DataType::from("value")]); 123 | /// } else { 124 | /// return Err(From::from("expected at least three records but got none")); 125 | /// } 126 | /// 127 | /// if let Some(result) = iter.next() { 128 | /// let row: Vec = result?; 129 | /// assert_eq!(row, [DataType::from("celsius"), DataType::from(22.2222)]); 130 | /// } else { 131 | /// return Err(From::from("expected at least three records but got one")); 132 | /// } 133 | /// 134 | /// Ok(()) 135 | /// } 136 | /// ``` 137 | pub fn has_headers(&mut self, yes: bool) -> &mut Self { 138 | if yes { 139 | self.headers = Headers::All; 140 | } else { 141 | self.headers = Headers::None; 142 | } 143 | self 144 | } 145 | } 146 | 147 | impl<'h, H: AsRef + Clone + 'h> RangeDeserializerBuilder<'h, H> { 148 | /// Build a `RangeDeserializer` from this configuration and keep only selected headers. 149 | /// 150 | /// # Example 151 | /// 152 | /// ``` 153 | /// # use calamine::{open_workbook, Error, Xlsx, Reader, RangeDeserializerBuilder}; 154 | /// fn main() -> Result<(), Error> { 155 | /// let path = format!("{}/tests/temperature.xlsx", env!("CARGO_MANIFEST_DIR")); 156 | /// let mut workbook: Xlsx<_> = open_workbook(path)?; 157 | /// let range = workbook.worksheet_range("Sheet1") 158 | /// .ok_or(Error::Msg("Cannot find 'Sheet1'"))??; 159 | /// let mut iter = RangeDeserializerBuilder::with_headers(&["value", "label"]).from_range(&range)?; 160 | /// 161 | /// if let Some(result) = iter.next() { 162 | /// let (value, label): (f64, String) = result?; 163 | /// assert_eq!(label, "celsius"); 164 | /// assert_eq!(value, 22.2222); 165 | /// 166 | /// Ok(()) 167 | /// } else { 168 | /// return Err(From::from("expected at least one record but got none")); 169 | /// } 170 | /// } 171 | /// ``` 172 | pub fn with_headers(headers: &'h [H]) -> Self { 173 | RangeDeserializerBuilder { 174 | headers: Headers::Custom(headers), 175 | } 176 | } 177 | 178 | /// Build a `RangeDeserializer` from this configuration. 179 | /// 180 | /// # Example 181 | /// 182 | /// ``` 183 | /// # use calamine::{open_workbook, Error, Xlsx, Reader, RangeDeserializerBuilder}; 184 | /// fn main() -> Result<(), Error> { 185 | /// let path = format!("{}/tests/temperature.xlsx", env!("CARGO_MANIFEST_DIR")); 186 | /// let mut workbook: Xlsx<_> = open_workbook(path)?; 187 | /// let range = workbook.worksheet_range("Sheet1") 188 | /// .ok_or(Error::Msg("Cannot find 'Sheet1'"))??; 189 | /// let mut iter = RangeDeserializerBuilder::new().from_range(&range)?; 190 | /// 191 | /// if let Some(result) = iter.next() { 192 | /// let (label, value): (String, f64) = result?; 193 | /// assert_eq!(label, "celsius"); 194 | /// assert_eq!(value, 22.2222); 195 | /// 196 | /// Ok(()) 197 | /// } else { 198 | /// return Err(From::from("expected at least one record but got none")); 199 | /// } 200 | /// } 201 | /// ``` 202 | pub fn from_range<'cell, T, D>( 203 | &self, 204 | range: &'cell Range, 205 | ) -> Result, DeError> 206 | where 207 | T: ToCellDeserializer<'cell>, 208 | D: DeserializeOwned, 209 | { 210 | RangeDeserializer::new(self, range) 211 | } 212 | } 213 | 214 | /// A configured `Range` deserializer. 215 | /// 216 | /// # Example 217 | /// 218 | /// ``` 219 | /// # use calamine::{open_workbook, Error, Xlsx, Reader, RangeDeserializerBuilder}; 220 | /// fn main() -> Result<(), Error> { 221 | /// let path = format!("{}/tests/temperature.xlsx", env!("CARGO_MANIFEST_DIR")); 222 | /// let mut workbook: Xlsx<_> = open_workbook(path)?; 223 | /// let range = workbook.worksheet_range("Sheet1") 224 | /// .ok_or(Error::Msg("Cannot find 'Sheet1'"))??; 225 | /// 226 | /// let mut iter = RangeDeserializerBuilder::new().from_range(&range)?; 227 | /// 228 | /// if let Some(result) = iter.next() { 229 | /// let (label, value): (String, f64) = result?; 230 | /// assert_eq!(label, "celsius"); 231 | /// assert_eq!(value, 22.2222); 232 | /// Ok(()) 233 | /// } else { 234 | /// Err(From::from("expected at least one record but got none")) 235 | /// } 236 | /// } 237 | /// ``` 238 | pub struct RangeDeserializer<'cell, T, D> 239 | where 240 | T: ToCellDeserializer<'cell>, 241 | D: DeserializeOwned, 242 | { 243 | column_indexes: Vec, 244 | headers: Option>, 245 | rows: Rows<'cell, T>, 246 | current_pos: (u32, u32), 247 | end_pos: (u32, u32), 248 | _priv: PhantomData, 249 | } 250 | 251 | impl<'cell, T, D> RangeDeserializer<'cell, T, D> 252 | where 253 | T: ToCellDeserializer<'cell>, 254 | D: DeserializeOwned, 255 | { 256 | fn new<'h, H: AsRef + Clone + 'h>( 257 | builder: &RangeDeserializerBuilder<'h, H>, 258 | range: &'cell Range, 259 | ) -> Result { 260 | let mut rows = range.rows(); 261 | 262 | let mut current_pos = range.start().unwrap_or((0, 0)); 263 | let end_pos = range.end().unwrap_or((0, 0)); 264 | 265 | let (column_indexes, headers) = match builder.headers { 266 | Headers::None => ((0..range.width()).collect(), None), 267 | Headers::All => { 268 | if let Some(row) = rows.next() { 269 | let all_indexes = (0..row.len()).collect::>(); 270 | let all_headers = { 271 | let de = RowDeserializer::new(&all_indexes, None, row, current_pos); 272 | current_pos.0 += 1; 273 | Deserialize::deserialize(de)? 274 | }; 275 | (all_indexes, Some(all_headers)) 276 | } else { 277 | (Vec::new(), None) 278 | } 279 | } 280 | Headers::Custom(headers) => { 281 | if let Some(row) = rows.next() { 282 | let all_indexes = (0..row.len()).collect::>(); 283 | let de = RowDeserializer::new(&all_indexes, None, row, current_pos); 284 | current_pos.0 += 1; 285 | let all_headers: Vec = Deserialize::deserialize(de)?; 286 | let custom_indexes = headers 287 | .iter() 288 | .map(|h| h.as_ref().trim()) 289 | .map(|h| { 290 | all_headers 291 | .iter() 292 | .position(|header| header.trim() == h) 293 | .ok_or_else(|| DeError::HeaderNotFound(h.to_owned())) 294 | }) 295 | .collect::, DeError>>()?; 296 | (custom_indexes, Some(all_headers)) 297 | } else { 298 | (Vec::new(), None) 299 | } 300 | } 301 | }; 302 | 303 | Ok(RangeDeserializer { 304 | column_indexes, 305 | headers, 306 | rows, 307 | current_pos, 308 | end_pos, 309 | _priv: PhantomData, 310 | }) 311 | } 312 | } 313 | 314 | impl<'cell, T, D> Iterator for RangeDeserializer<'cell, T, D> 315 | where 316 | T: ToCellDeserializer<'cell>, 317 | D: DeserializeOwned, 318 | { 319 | type Item = Result; 320 | 321 | fn next(&mut self) -> Option { 322 | let RangeDeserializer { 323 | ref column_indexes, 324 | ref headers, 325 | ref mut rows, 326 | mut current_pos, 327 | .. 328 | } = *self; 329 | 330 | if let Some(row) = rows.next() { 331 | current_pos.0 += 1; 332 | let headers = headers.as_ref().map(|h| &**h); 333 | let de = RowDeserializer::new(column_indexes, headers, row, current_pos); 334 | Some(Deserialize::deserialize(de)) 335 | } else { 336 | None 337 | } 338 | } 339 | 340 | fn size_hint(&self) -> (usize, Option) { 341 | let remaining = (self.end_pos.0 - self.current_pos.0) as usize; 342 | 343 | (remaining, Some(remaining)) 344 | } 345 | } 346 | 347 | struct RowDeserializer<'header, 'cell, T> 348 | where 349 | T: ToCellDeserializer<'cell>, 350 | { 351 | cells: &'cell [T], 352 | headers: Option<&'header [String]>, 353 | iter: slice::Iter<'header, usize>, // iterator over column indexes 354 | peek: Option, 355 | pos: (u32, u32), 356 | } 357 | 358 | impl<'header, 'cell, T> RowDeserializer<'header, 'cell, T> 359 | where 360 | T: 'cell + ToCellDeserializer<'cell>, 361 | { 362 | fn new( 363 | column_indexes: &'header [usize], 364 | headers: Option<&'header [String]>, 365 | cells: &'cell [T], 366 | pos: (u32, u32), 367 | ) -> Self { 368 | RowDeserializer { 369 | iter: column_indexes.iter(), 370 | headers, 371 | cells, 372 | pos, 373 | peek: None, 374 | } 375 | } 376 | 377 | fn has_headers(&self) -> bool { 378 | self.headers.is_some() 379 | } 380 | } 381 | 382 | impl<'de, 'header, 'cell, T> serde::Deserializer<'de> for RowDeserializer<'header, 'cell, T> 383 | where 384 | 'header: 'de, 385 | 'cell: 'de, 386 | T: 'cell + ToCellDeserializer<'cell>, 387 | { 388 | type Error = DeError; 389 | 390 | fn deserialize_any(self, visitor: V) -> Result 391 | where 392 | V: Visitor<'de>, 393 | { 394 | visitor.visit_seq(self) 395 | } 396 | 397 | fn deserialize_map>(self, visitor: V) -> Result { 398 | if !self.has_headers() { 399 | visitor.visit_seq(self) 400 | } else { 401 | visitor.visit_map(self) 402 | } 403 | } 404 | 405 | fn deserialize_struct>( 406 | self, 407 | _name: &'static str, 408 | _cells: &'static [&'static str], 409 | visitor: V, 410 | ) -> Result { 411 | if !self.has_headers() { 412 | visitor.visit_seq(self) 413 | } else { 414 | visitor.visit_map(self) 415 | } 416 | } 417 | 418 | forward_to_deserialize_any! { 419 | bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes 420 | byte_buf option unit unit_struct newtype_struct seq tuple 421 | tuple_struct enum identifier ignored_any 422 | } 423 | } 424 | 425 | impl<'de, 'header, 'cell, T> SeqAccess<'de> for RowDeserializer<'header, 'cell, T> 426 | where 427 | 'header: 'de, 428 | 'cell: 'de, 429 | T: ToCellDeserializer<'cell>, 430 | { 431 | type Error = DeError; 432 | 433 | fn next_element_seed(&mut self, seed: D) -> Result, Self::Error> 434 | where 435 | D: DeserializeSeed<'de>, 436 | { 437 | match self.iter.next().map(|i| &self.cells[*i]) { 438 | Some(value) => { 439 | let de = value.to_cell_deserializer(self.pos); 440 | seed.deserialize(de).map(Some) 441 | } 442 | None => Ok(None), 443 | } 444 | } 445 | 446 | fn size_hint(&self) -> Option { 447 | match self.iter.size_hint() { 448 | (lower, Some(upper)) if lower == upper => Some(upper), 449 | _ => None, 450 | } 451 | } 452 | } 453 | 454 | impl<'de, 'header: 'de, 'cell: 'de, T> de::MapAccess<'de> for RowDeserializer<'header, 'cell, T> 455 | where 456 | 'header: 'de, 457 | 'cell: 'de, 458 | T: ToCellDeserializer<'cell>, 459 | { 460 | type Error = DeError; 461 | 462 | fn next_key_seed>( 463 | &mut self, 464 | seed: K, 465 | ) -> Result, Self::Error> { 466 | let headers = self 467 | .headers 468 | .expect("Cannot map-deserialize range without headers"); 469 | 470 | while let Some(i) = self.iter.next() { 471 | if !self.cells[*i].is_empty() { 472 | self.peek = Some(*i); 473 | let de = BorrowedStrDeserializer::::new(&headers[*i]); 474 | return seed.deserialize(de).map(Some); 475 | } 476 | } 477 | Ok(None) 478 | } 479 | 480 | fn next_value_seed>( 481 | &mut self, 482 | seed: K, 483 | ) -> Result { 484 | let cell = self 485 | .peek 486 | .take() 487 | .map(|i| &self.cells[i]) 488 | .ok_or(DeError::UnexpectedEndOfRow { pos: self.pos })?; 489 | let de = cell.to_cell_deserializer(self.pos); 490 | seed.deserialize(de) 491 | } 492 | } 493 | 494 | /// Constructs a deserializer for a `CellType`. 495 | pub trait ToCellDeserializer<'a>: CellType { 496 | /// The deserializer. 497 | type Deserializer: for<'de> serde::Deserializer<'de, Error = DeError>; 498 | 499 | /// Construct a `CellType` deserializer at the specified position. 500 | fn to_cell_deserializer(&'a self, pos: (u32, u32)) -> Self::Deserializer; 501 | 502 | /// Assess if the cell is empty. 503 | fn is_empty(&self) -> bool; 504 | } 505 | 506 | impl<'a> ToCellDeserializer<'a> for DataType { 507 | type Deserializer = DataTypeDeserializer<'a>; 508 | 509 | fn to_cell_deserializer(&'a self, pos: (u32, u32)) -> DataTypeDeserializer<'a> { 510 | DataTypeDeserializer { 511 | data_type: self, 512 | pos, 513 | } 514 | } 515 | 516 | #[inline] 517 | fn is_empty(&self) -> bool { 518 | if let DataType::Empty = self { 519 | true 520 | } else { 521 | false 522 | } 523 | } 524 | } 525 | 526 | macro_rules! deserialize_num { 527 | ($typ:ty, $method:ident, $visit:ident) => { 528 | fn $method(self, visitor: V) -> Result 529 | where 530 | V: Visitor<'de>, 531 | { 532 | match self.data_type { 533 | DataType::Float(v) => visitor.$visit(*v as $typ), 534 | DataType::Int(v) => visitor.$visit(*v as $typ), 535 | DataType::String(ref s) => { 536 | let v = s.parse().map_err(|_| { 537 | DeError::Custom(format!("Expecting {}, got '{}'", stringify!($typ), s)) 538 | })?; 539 | visitor.$visit(v) 540 | } 541 | DataType::Error(ref err) => Err(DeError::CellError { 542 | err: err.clone(), 543 | pos: self.pos, 544 | }), 545 | ref d => Err(DeError::Custom(format!( 546 | "Expecting {}, got {:?}", 547 | stringify!($typ), 548 | d 549 | ))), 550 | } 551 | } 552 | }; 553 | } 554 | 555 | /// A deserializer for the `DataType` type. 556 | pub struct DataTypeDeserializer<'a> { 557 | data_type: &'a DataType, 558 | pos: (u32, u32), 559 | } 560 | 561 | impl<'a, 'de> serde::Deserializer<'de> for DataTypeDeserializer<'a> { 562 | type Error = DeError; 563 | 564 | fn deserialize_any(self, visitor: V) -> Result 565 | where 566 | V: Visitor<'de>, 567 | { 568 | match self.data_type { 569 | DataType::String(v) => visitor.visit_str(v), 570 | DataType::Float(v) => visitor.visit_f64(*v), 571 | DataType::Bool(v) => visitor.visit_bool(*v), 572 | DataType::Int(v) => visitor.visit_i64(*v), 573 | DataType::Empty => visitor.visit_unit(), 574 | DataType::DateTime(v) => visitor.visit_f64(*v), 575 | DataType::Error(ref err) => Err(DeError::CellError { 576 | err: err.clone(), 577 | pos: self.pos, 578 | }), 579 | } 580 | } 581 | 582 | fn deserialize_str(self, visitor: V) -> Result 583 | where 584 | V: Visitor<'de>, 585 | { 586 | match self.data_type { 587 | DataType::String(v) => visitor.visit_str(v), 588 | DataType::Empty => visitor.visit_str(""), 589 | DataType::Float(v) => visitor.visit_str(&v.to_string()), 590 | DataType::Int(v) => visitor.visit_str(&v.to_string()), 591 | DataType::Bool(v) => visitor.visit_str(&v.to_string()), 592 | DataType::DateTime(v) => visitor.visit_str(&v.to_string()), 593 | DataType::Error(ref err) => Err(DeError::CellError { 594 | err: err.clone(), 595 | pos: self.pos, 596 | }), 597 | } 598 | } 599 | 600 | fn deserialize_bytes(self, visitor: V) -> Result 601 | where 602 | V: Visitor<'de>, 603 | { 604 | match self.data_type { 605 | DataType::String(v) => visitor.visit_bytes(v.as_bytes()), 606 | DataType::Empty => visitor.visit_bytes(&[]), 607 | DataType::Error(ref err) => Err(DeError::CellError { 608 | err: err.clone(), 609 | pos: self.pos, 610 | }), 611 | ref d => Err(DeError::Custom(format!("Expecting bytes, got {:?}", d))), 612 | } 613 | } 614 | 615 | fn deserialize_byte_buf(self, visitor: V) -> Result 616 | where 617 | V: Visitor<'de>, 618 | { 619 | self.deserialize_bytes(visitor) 620 | } 621 | 622 | fn deserialize_string(self, visitor: V) -> Result 623 | where 624 | V: Visitor<'de>, 625 | { 626 | self.deserialize_str(visitor) 627 | } 628 | 629 | fn deserialize_bool(self, visitor: V) -> Result 630 | where 631 | V: Visitor<'de>, 632 | { 633 | match self.data_type { 634 | DataType::Bool(v) => visitor.visit_bool(*v), 635 | DataType::String(ref v) => match &**v { 636 | "TRUE" | "true" | "True" => visitor.visit_bool(true), 637 | "FALSE" | "false" | "False" => visitor.visit_bool(false), 638 | d => Err(DeError::Custom(format!("Expecting bool, got '{}'", d))), 639 | }, 640 | DataType::Empty => visitor.visit_bool(false), 641 | DataType::Float(v) => visitor.visit_bool(*v != 0.), 642 | DataType::Int(v) => visitor.visit_bool(*v != 0), 643 | DataType::DateTime(v) => visitor.visit_bool(*v != 0.), 644 | DataType::Error(ref err) => Err(DeError::CellError { 645 | err: err.clone(), 646 | pos: self.pos, 647 | }), 648 | } 649 | } 650 | 651 | fn deserialize_char(self, visitor: V) -> Result 652 | where 653 | V: Visitor<'de>, 654 | { 655 | match self.data_type { 656 | DataType::String(ref s) if s.len() == 1 => { 657 | visitor.visit_char(s.chars().next().expect("s not empty")) 658 | } 659 | DataType::Error(ref err) => Err(DeError::CellError { 660 | err: err.clone(), 661 | pos: self.pos, 662 | }), 663 | ref d => Err(DeError::Custom(format!("Expecting unit, got {:?}", d))), 664 | } 665 | } 666 | 667 | fn deserialize_unit(self, visitor: V) -> Result 668 | where 669 | V: Visitor<'de>, 670 | { 671 | match self.data_type { 672 | DataType::Empty => visitor.visit_unit(), 673 | DataType::Error(ref err) => Err(DeError::CellError { 674 | err: err.clone(), 675 | pos: self.pos, 676 | }), 677 | ref d => Err(DeError::Custom(format!("Expecting unit, got {:?}", d))), 678 | } 679 | } 680 | 681 | fn deserialize_option(self, visitor: V) -> Result 682 | where 683 | V: Visitor<'de>, 684 | { 685 | match self.data_type { 686 | DataType::Empty => visitor.visit_none(), 687 | _ => visitor.visit_some(self), 688 | } 689 | } 690 | 691 | fn deserialize_newtype_struct( 692 | self, 693 | _name: &'static str, 694 | visitor: V, 695 | ) -> Result 696 | where 697 | V: Visitor<'de>, 698 | { 699 | visitor.visit_newtype_struct(self) 700 | } 701 | 702 | deserialize_num!(i64, deserialize_i64, visit_i64); 703 | deserialize_num!(i32, deserialize_i32, visit_i32); 704 | deserialize_num!(i16, deserialize_i16, visit_i16); 705 | deserialize_num!(i8, deserialize_i8, visit_i8); 706 | deserialize_num!(u64, deserialize_u64, visit_u64); 707 | deserialize_num!(u32, deserialize_u32, visit_u32); 708 | deserialize_num!(u16, deserialize_u16, visit_u16); 709 | deserialize_num!(u8, deserialize_u8, visit_u8); 710 | deserialize_num!(f64, deserialize_f64, visit_f64); 711 | deserialize_num!(f32, deserialize_f32, visit_f32); 712 | 713 | forward_to_deserialize_any! { 714 | unit_struct seq tuple tuple_struct map struct enum identifier ignored_any 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! A module to provide a convenient wrapper around all error types 2 | 3 | /// A struct to handle any error and a message 4 | #[derive(Debug)] 5 | pub enum Error { 6 | /// IO error 7 | Io(std::io::Error), 8 | 9 | /// Ods specific error 10 | Ods(crate::ods::OdsError), 11 | /// xls specific error 12 | Xls(crate::xls::XlsError), 13 | /// xlsb specific error 14 | Xlsb(crate::xlsb::XlsbError), 15 | /// xlsx specific error 16 | Xlsx(crate::xlsx::XlsxError), 17 | /// vba specific error 18 | Vba(crate::vba::VbaError), 19 | /// cfb specific error 20 | De(crate::de::DeError), 21 | 22 | /// General error message 23 | Msg(&'static str), 24 | } 25 | 26 | from_err!(std::io::Error, Error, Io); 27 | from_err!(crate::ods::OdsError, Error, Ods); 28 | from_err!(crate::xls::XlsError, Error, Xls); 29 | from_err!(crate::xlsb::XlsbError, Error, Xlsb); 30 | from_err!(crate::xlsx::XlsxError, Error, Xlsx); 31 | from_err!(crate::vba::VbaError, Error, Vba); 32 | from_err!(crate::de::DeError, Error, De); 33 | from_err!(&'static str, Error, Msg); 34 | 35 | impl std::fmt::Display for Error { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | Error::Io(e) => write!(f, "I/O error: {}", e), 39 | Error::Ods(e) => write!(f, "Ods error: {}", e), 40 | Error::Xls(e) => write!(f, "Xls error: {}", e), 41 | Error::Xlsx(e) => write!(f, "Xlsx error: {}", e), 42 | Error::Xlsb(e) => write!(f, "Xlsb error: {}", e), 43 | Error::Vba(e) => write!(f, "Vba error: {}", e), 44 | Error::De(e) => write!(f, "Deserializer error: {}", e), 45 | Error::Msg(msg) => write!(f, "{}", msg), 46 | } 47 | } 48 | } 49 | 50 | impl std::error::Error for Error { 51 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 52 | match self { 53 | Error::Io(e) => Some(e), 54 | Error::Ods(e) => Some(e), 55 | Error::Xls(e) => Some(e), 56 | Error::Xlsb(e) => Some(e), 57 | Error::Xlsx(e) => Some(e), 58 | Error::Vba(e) => Some(e), 59 | Error::De(e) => Some(e), 60 | _ => None, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/ods.rs: -------------------------------------------------------------------------------- 1 | //! A module to parse Open Document Spreadsheets 2 | //! 3 | //! # Reference 4 | //! OASIS Open Document Format for Office Application 1.2 (ODF 1.2) 5 | //! http://docs.oasis-open.org/office/v1.2/OpenDocument-v1.2.pdf 6 | 7 | use std::borrow::Cow; 8 | use std::collections::HashMap; 9 | use std::io::{BufReader, Read, Seek}; 10 | 11 | use quick_xml::events::attributes::Attributes; 12 | use quick_xml::events::Event; 13 | use quick_xml::Reader as XmlReader; 14 | use zip::read::{ZipArchive, ZipFile}; 15 | use zip::result::ZipError; 16 | 17 | use crate::vba::VbaProject; 18 | use crate::{DataType, Metadata, Range, Reader}; 19 | use std::marker::PhantomData; 20 | 21 | const MIMETYPE: &[u8] = b"application/vnd.oasis.opendocument.spreadsheet"; 22 | 23 | type OdsReader<'a> = XmlReader>>; 24 | 25 | /// An enum for ods specific errors 26 | #[derive(Debug)] 27 | pub enum OdsError { 28 | /// Io error 29 | Io(std::io::Error), 30 | /// Zip error 31 | Zip(zip::result::ZipError), 32 | /// Xml error 33 | Xml(quick_xml::Error), 34 | /// Error while parsing string 35 | Parse(std::string::ParseError), 36 | /// Error while parsing integer 37 | ParseInt(std::num::ParseIntError), 38 | /// Error while parsing float 39 | ParseFloat(std::num::ParseFloatError), 40 | 41 | /// Invalid MIME 42 | InvalidMime(Vec), 43 | /// File not found 44 | FileNotFound(&'static str), 45 | /// Unexpected end of file 46 | Eof(&'static str), 47 | /// Unexpexted error 48 | Mismatch { 49 | /// Expected 50 | expected: &'static str, 51 | /// Found 52 | found: String, 53 | }, 54 | } 55 | 56 | from_err!(std::io::Error, OdsError, Io); 57 | from_err!(zip::result::ZipError, OdsError, Zip); 58 | from_err!(quick_xml::Error, OdsError, Xml); 59 | from_err!(std::string::ParseError, OdsError, Parse); 60 | from_err!(std::num::ParseFloatError, OdsError, ParseFloat); 61 | 62 | impl std::fmt::Display for OdsError { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | match self { 65 | OdsError::Io(e) => write!(f, "I/O error: {}", e), 66 | OdsError::Zip(e) => write!(f, "Zip error: {}", e), 67 | OdsError::Xml(e) => write!(f, "Xml error: {}", e), 68 | OdsError::Parse(e) => write!(f, "Parse string error: {}", e), 69 | OdsError::ParseInt(e) => write!(f, "Parse integer error: {}", e), 70 | OdsError::ParseFloat(e) => write!(f, "Parse float error: {}", e), 71 | OdsError::InvalidMime(mime) => write!(f, "Invalid MIME type: {:?}", mime), 72 | OdsError::FileNotFound(file) => write!(f, "'{}' file not found in archive", file), 73 | OdsError::Eof(node) => write!(f, "Expecting '{}' node, found end of xml file", node), 74 | OdsError::Mismatch { expected, found } => { 75 | write!(f, "Expecting '{}', found '{}'", expected, found) 76 | } 77 | } 78 | } 79 | } 80 | 81 | impl std::error::Error for OdsError { 82 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 83 | match self { 84 | OdsError::Io(e) => Some(e), 85 | OdsError::Zip(e) => Some(e), 86 | OdsError::Xml(e) => Some(e), 87 | OdsError::Parse(e) => Some(e), 88 | OdsError::ParseInt(e) => Some(e), 89 | OdsError::ParseFloat(e) => Some(e), 90 | _ => None, 91 | } 92 | } 93 | } 94 | 95 | /// An OpenDocument Spreadsheet document parser 96 | /// 97 | /// # Reference 98 | /// OASIS Open Document Format for Office Application 1.2 (ODF 1.2) 99 | /// http://docs.oasis-open.org/office/v1.2/OpenDocument-v1.2.pdf 100 | pub struct Ods 101 | where 102 | RS: Read + Seek, 103 | { 104 | sheets: HashMap, Range)>, 105 | metadata: Metadata, 106 | marker: PhantomData, 107 | } 108 | 109 | impl Reader for Ods { 110 | type RS = RS; 111 | type Error = OdsError; 112 | 113 | fn new(reader: RS) -> Result 114 | where 115 | RS: Read + Seek, 116 | { 117 | let mut zip = ZipArchive::new(reader)?; 118 | 119 | // check mimetype 120 | match zip.by_name("mimetype") { 121 | Ok(mut f) => { 122 | let mut buf = [0u8; 46]; 123 | f.read_exact(&mut buf)?; 124 | if &buf[..] != MIMETYPE { 125 | return Err(OdsError::InvalidMime(buf.to_vec())); 126 | } 127 | } 128 | Err(ZipError::FileNotFound) => return Err(OdsError::FileNotFound("mimetype")), 129 | Err(e) => return Err(OdsError::Zip(e)), 130 | } 131 | 132 | let Content { 133 | sheets, 134 | sheet_names, 135 | defined_names, 136 | } = parse_content(zip)?; 137 | let metadata = Metadata { 138 | sheets: sheet_names, 139 | names: defined_names, 140 | }; 141 | 142 | Ok(Ods { 143 | marker: PhantomData, 144 | metadata, 145 | sheets, 146 | }) 147 | } 148 | 149 | /// Gets `VbaProject` 150 | fn vba_project(&mut self) -> Option, OdsError>> { 151 | None 152 | } 153 | 154 | /// Read sheets from workbook.xml and get their corresponding path from relationships 155 | fn metadata(&self) -> &Metadata { 156 | &self.metadata 157 | } 158 | 159 | /// Read worksheet data in corresponding worksheet path 160 | fn worksheet_range(&mut self, name: &str) -> Option, OdsError>> { 161 | self.sheets.get(name).map(|r| Ok(r.0.to_owned())) 162 | } 163 | 164 | /// Read worksheet data in corresponding worksheet path 165 | fn worksheet_formula(&mut self, name: &str) -> Option, OdsError>> { 166 | self.sheets.get(name).map(|r| Ok(r.1.to_owned())) 167 | } 168 | 169 | fn worksheets(&mut self) -> Vec<(String, Range)> { 170 | self.sheets 171 | .iter() 172 | .map(|(name, (range, _formula))| (name.to_owned(), range.clone())) 173 | .collect() 174 | } 175 | } 176 | 177 | struct Content { 178 | sheets: HashMap, Range)>, 179 | sheet_names: Vec, 180 | defined_names: Vec<(String, String)>, 181 | } 182 | 183 | /// Parses content.xml and store the result in `self.content` 184 | fn parse_content(mut zip: ZipArchive) -> Result { 185 | let mut reader = match zip.by_name("content.xml") { 186 | Ok(f) => { 187 | let mut r = XmlReader::from_reader(BufReader::new(f)); 188 | r.check_end_names(false) 189 | .trim_text(true) 190 | .check_comments(false) 191 | .expand_empty_elements(true); 192 | r 193 | } 194 | Err(ZipError::FileNotFound) => return Err(OdsError::FileNotFound("content.xml")), 195 | Err(e) => return Err(OdsError::Zip(e)), 196 | }; 197 | let mut buf = Vec::new(); 198 | let mut sheets = HashMap::new(); 199 | let mut defined_names = Vec::new(); 200 | let mut sheet_names = Vec::new(); 201 | loop { 202 | match reader.read_event(&mut buf) { 203 | Ok(Event::Start(ref e)) if e.name() == b"table:table" => { 204 | if let Some(ref a) = e 205 | .attributes() 206 | .filter_map(|a| a.ok()) 207 | .find(|a| a.key == b"table:name") 208 | { 209 | let name = a 210 | .unescape_and_decode_value(&reader) 211 | .map_err(OdsError::Xml)?; 212 | let (range, formulas) = read_table(&mut reader)?; 213 | sheet_names.push(name.clone()); 214 | sheets.insert(name, (range, formulas)); 215 | } 216 | } 217 | Ok(Event::Start(ref e)) if e.name() == b"table:named-expressions" => { 218 | defined_names = read_named_expressions(&mut reader)?; 219 | } 220 | Ok(Event::Eof) => break, 221 | Err(e) => return Err(OdsError::Xml(e)), 222 | _ => (), 223 | } 224 | buf.clear(); 225 | } 226 | Ok(Content { 227 | sheets, 228 | sheet_names, 229 | defined_names, 230 | }) 231 | } 232 | 233 | fn read_table(reader: &mut OdsReader<'_>) -> Result<(Range, Range), OdsError> { 234 | let mut cells = Vec::new(); 235 | let mut formulas = Vec::new(); 236 | let mut cols = Vec::new(); 237 | let mut buf = Vec::new(); 238 | let mut row_buf = Vec::new(); 239 | let mut cell_buf = Vec::new(); 240 | cols.push(0); 241 | loop { 242 | match reader.read_event(&mut buf) { 243 | Ok(Event::Start(ref e)) if e.name() == b"table:table-row" => { 244 | read_row( 245 | reader, 246 | &mut row_buf, 247 | &mut cell_buf, 248 | &mut cells, 249 | &mut formulas, 250 | )?; 251 | cols.push(cells.len()); 252 | } 253 | Ok(Event::End(ref e)) if e.name() == b"table:table" => break, 254 | Err(e) => return Err(OdsError::Xml(e)), 255 | Ok(_) => (), 256 | } 257 | buf.clear(); 258 | } 259 | Ok((get_range(cells, &cols), get_range(formulas, &cols))) 260 | } 261 | 262 | fn get_range(mut cells: Vec, cols: &[usize]) -> Range { 263 | // find smallest area with non empty Cells 264 | let mut row_min = None; 265 | let mut row_max = 0; 266 | let mut col_min = std::usize::MAX; 267 | let mut col_max = 0; 268 | { 269 | for (i, w) in cols.windows(2).enumerate() { 270 | let row = &cells[w[0]..w[1]]; 271 | if let Some(p) = row.iter().position(|c| c != &T::default()) { 272 | if row_min.is_none() { 273 | row_min = Some(i); 274 | } 275 | row_max = i; 276 | if p < col_min { 277 | col_min = p; 278 | } 279 | if let Some(p) = row.iter().rposition(|c| c != &T::default()) { 280 | if p > col_max { 281 | col_max = p; 282 | } 283 | } 284 | } 285 | } 286 | } 287 | let row_min = match row_min { 288 | Some(min) => min, 289 | _ => return Range::default(), 290 | }; 291 | 292 | // rebuild cells into its smallest non empty area 293 | let cells_len = (row_max + 1 - row_min) * (col_max + 1 - col_min); 294 | if cells.len() != cells_len { 295 | let mut new_cells = Vec::with_capacity(cells_len); 296 | let empty_cells = vec![T::default(); col_max + 1]; 297 | for w in cols.windows(2).skip(row_min).take(row_max + 1) { 298 | let row = &cells[w[0]..w[1]]; 299 | match row.len().cmp(&(col_max + 1)) { 300 | std::cmp::Ordering::Less => { 301 | new_cells.extend_from_slice(&row[col_min..]); 302 | new_cells.extend_from_slice(&empty_cells[row.len()..]); 303 | } 304 | std::cmp::Ordering::Equal => { 305 | new_cells.extend_from_slice(&row[col_min..]); 306 | } 307 | std::cmp::Ordering::Greater => { 308 | new_cells.extend_from_slice(&row[col_min..=col_max]); 309 | } 310 | } 311 | } 312 | cells = new_cells; 313 | } 314 | Range { 315 | start: (row_min as u32, col_min as u32), 316 | end: (row_max as u32, col_max as u32), 317 | inner: cells, 318 | } 319 | } 320 | 321 | fn read_row( 322 | reader: &mut OdsReader<'_>, 323 | row_buf: &mut Vec, 324 | cell_buf: &mut Vec, 325 | cells: &mut Vec, 326 | formulas: &mut Vec, 327 | ) -> Result<(), OdsError> { 328 | loop { 329 | row_buf.clear(); 330 | match reader.read_event(row_buf) { 331 | Ok(Event::Start(ref e)) 332 | if e.name() == b"table:table-cell" || e.name() == b"table:covered-table-cell" => 333 | { 334 | let mut repeats = 1; 335 | for a in e.attributes() { 336 | let a = a?; 337 | if a.key == b"table:number-columns-repeated" { 338 | repeats = reader 339 | .decode(&a.value) 340 | .parse() 341 | .map_err(OdsError::ParseInt)?; 342 | break; 343 | } 344 | } 345 | 346 | let (value, formula, is_closed) = get_datatype(reader, e.attributes(), cell_buf)?; 347 | for _ in 0..repeats { 348 | cells.push(value.clone()); 349 | formulas.push(formula.clone()); 350 | } 351 | if !is_closed { 352 | reader.read_to_end(e.name(), cell_buf)?; 353 | } 354 | } 355 | Ok(Event::End(ref e)) if e.name() == b"table:table-row" => break, 356 | Err(e) => return Err(OdsError::Xml(e)), 357 | Ok(e) => { 358 | return Err(OdsError::Mismatch { 359 | expected: "table-cell", 360 | found: format!("{:?}", e), 361 | }); 362 | } 363 | } 364 | } 365 | Ok(()) 366 | } 367 | 368 | /// Converts table-cell element into a `DataType` 369 | /// 370 | /// ODF 1.2-19.385 371 | fn get_datatype( 372 | reader: &mut OdsReader<'_>, 373 | atts: Attributes<'_>, 374 | buf: &mut Vec, 375 | ) -> Result<(DataType, String, bool), OdsError> { 376 | let mut is_string = false; 377 | let mut is_value_set = false; 378 | let mut val = DataType::Empty; 379 | let mut formula = String::new(); 380 | for a in atts { 381 | let a = a?; 382 | match a.key { 383 | b"office:value" if !is_value_set => { 384 | let v = reader.decode(&a.value); 385 | val = DataType::Float(v.parse().map_err(OdsError::ParseFloat)?); 386 | is_value_set = true; 387 | } 388 | b"office:string-value" | b"office:date-value" | b"office:time-value" 389 | if !is_value_set => 390 | { 391 | val = DataType::String(a.unescape_and_decode_value(reader).map_err(OdsError::Xml)?); 392 | is_value_set = true; 393 | } 394 | b"office:boolean-value" if !is_value_set => { 395 | let b = &*a.value == b"TRUE" || &*a.value == b"true"; 396 | val = DataType::Bool(b); 397 | is_value_set = true; 398 | } 399 | b"office:value-type" if !is_value_set => is_string = &*a.value == b"string", 400 | b"table:formula" => { 401 | formula = a.unescape_and_decode_value(reader).map_err(OdsError::Xml)?; 402 | } 403 | _ => (), 404 | } 405 | } 406 | if !is_value_set && is_string { 407 | // If the value type is string and the office:string-value attribute 408 | // is not present, the element content defines the value. 409 | let mut s = String::new(); 410 | let mut first_paragraph = true; 411 | loop { 412 | buf.clear(); 413 | match reader.read_event(buf) { 414 | Ok(Event::Text(ref e)) => { 415 | s.push_str(&e.unescape_and_decode(reader)?); 416 | } 417 | Ok(Event::End(ref e)) 418 | if e.name() == b"table:table-cell" 419 | || e.name() == b"table:covered-table-cell" => 420 | { 421 | return Ok((DataType::String(s), formula, true)); 422 | } 423 | Ok(Event::Start(ref e)) if e.name() == b"text:p" => { 424 | if first_paragraph { 425 | first_paragraph = false; 426 | } else { 427 | s.push('\n'); 428 | } 429 | } 430 | Ok(Event::Start(ref e)) if e.name() == b"text:s" => { 431 | s.push(' '); 432 | } 433 | Err(e) => return Err(OdsError::Xml(e)), 434 | Ok(Event::Eof) => return Err(OdsError::Eof("table:table-cell")), 435 | _ => (), 436 | } 437 | } 438 | } else { 439 | Ok((val, formula, false)) 440 | } 441 | } 442 | 443 | fn read_named_expressions(reader: &mut OdsReader<'_>) -> Result, OdsError> { 444 | let mut defined_names = Vec::new(); 445 | let mut buf = Vec::new(); 446 | loop { 447 | buf.clear(); 448 | match reader.read_event(&mut buf) { 449 | Ok(Event::Start(ref e)) 450 | if e.name() == b"table:named-range" || e.name() == b"table:named-expression" => 451 | { 452 | let mut name = String::new(); 453 | let mut formula = String::new(); 454 | for a in e.attributes() { 455 | let a = a.map_err(OdsError::Xml)?; 456 | match a.key { 457 | b"table:name" => { 458 | name = a.unescape_and_decode_value(reader).map_err(OdsError::Xml)? 459 | } 460 | b"table:cell-range-address" | b"table:expression" => { 461 | formula = a.unescape_and_decode_value(reader).map_err(OdsError::Xml)? 462 | } 463 | _ => (), 464 | } 465 | } 466 | defined_names.push((name, formula)); 467 | } 468 | Ok(Event::End(ref e)) 469 | if e.name() == b"table:named-range" || e.name() == b"table:named-expression" => {} 470 | Ok(Event::End(ref e)) if e.name() == b"table:named-expressions" => break, 471 | Err(e) => return Err(OdsError::Xml(e)), 472 | Ok(e) => { 473 | return Err(OdsError::Mismatch { 474 | expected: "table:named-expressions", 475 | found: format!("{:?}", e), 476 | }); 477 | } 478 | } 479 | } 480 | Ok(defined_names) 481 | } 482 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Internal module providing handy function 2 | 3 | use std::convert::TryInto; 4 | 5 | macro_rules! from_err { 6 | ($from:ty, $to:tt, $var:tt) => { 7 | impl From<$from> for $to { 8 | fn from(e: $from) -> $to { 9 | $to::$var(e) 10 | } 11 | } 12 | }; 13 | } 14 | 15 | /// Converts a &[u8] into an iterator of `u32`s 16 | pub fn to_u32(s: &[u8]) -> impl ExactSizeIterator + '_ { 17 | assert_eq!(s.len() % 4, 0); 18 | s.chunks_exact(4) 19 | .map(|data| u32::from_le_bytes(data.try_into().unwrap())) 20 | } 21 | 22 | #[inline] 23 | pub fn read_u32(s: &[u8]) -> u32 { 24 | u32::from_le_bytes(s[..4].try_into().unwrap()) 25 | } 26 | 27 | #[inline] 28 | pub fn read_i32(s: &[u8]) -> i32 { 29 | i32::from_le_bytes(s[..4].try_into().unwrap()) 30 | } 31 | 32 | #[inline] 33 | pub fn read_u16(s: &[u8]) -> u16 { 34 | u16::from_le_bytes(s[..2].try_into().unwrap()) 35 | } 36 | 37 | #[inline] 38 | pub fn read_u64(s: &[u8]) -> u64 { 39 | u64::from_le_bytes(s[..8].try_into().unwrap()) 40 | } 41 | 42 | #[inline] 43 | pub fn read_usize(s: &[u8]) -> usize { 44 | read_u32(s).try_into().unwrap() 45 | } 46 | 47 | #[inline] 48 | pub fn read_f64(s: &[u8]) -> f64 { 49 | f64::from_le_bytes(s[..8].try_into().unwrap()) 50 | } 51 | 52 | /// Push literal column into a String buffer 53 | pub fn push_column(mut col: u32, buf: &mut String) { 54 | if col < 26 { 55 | buf.push((b'A' + col as u8) as char); 56 | } else { 57 | let mut rev = String::new(); 58 | while col >= 26 { 59 | let c = col % 26; 60 | rev.push((b'A' + c as u8) as char); 61 | col -= c; 62 | col /= 26; 63 | } 64 | buf.extend(rev.chars().rev()); 65 | } 66 | } 67 | 68 | pub const FTAB_LEN: usize = 485; 69 | 70 | /* [MS-XLS] 2.5.198.17 */ 71 | /* [MS-XLSB] 2.5.97.10 */ 72 | pub const FTAB: [&str; FTAB_LEN] = [ 73 | "COUNT", 74 | "IF", 75 | "ISNA", 76 | "ISERROR", 77 | "SUM", 78 | "AVERAGE", 79 | "MIN", 80 | "MAX", 81 | "ROW", 82 | "COLUMN", 83 | "NA", 84 | "NPV", 85 | "STDEV", 86 | "DOLLAR", 87 | "FIXED", 88 | "SIN", 89 | "COS", 90 | "TAN", 91 | "ATAN", 92 | "PI", 93 | "SQRT", 94 | "EXP", 95 | "LN", 96 | "LOG10", 97 | "ABS", 98 | "INT", 99 | "SIGN", 100 | "ROUND", 101 | "LOOKUP", 102 | "INDEX", 103 | "REPT", 104 | "MID", 105 | "LEN", 106 | "VALUE", 107 | "TRUE", 108 | "FALSE", 109 | "AND", 110 | "OR", 111 | "NOT", 112 | "MOD", 113 | "DCOUNT", 114 | "DSUM", 115 | "DAVERAGE", 116 | "DMIN", 117 | "DMAX", 118 | "DSTDEV", 119 | "VAR", 120 | "DVAR", 121 | "TEXT", 122 | "LINEST", 123 | "TREND", 124 | "LOGEST", 125 | "GROWTH", 126 | "GOTO", 127 | "HALT", 128 | "RETURN", 129 | "PV", 130 | "FV", 131 | "NPER", 132 | "PMT", 133 | "RATE", 134 | "MIRR", 135 | "IRR", 136 | "RAND", 137 | "MATCH", 138 | "DATE", 139 | "TIME", 140 | "DAY", 141 | "MONTH", 142 | "YEAR", 143 | "WEEKDAY", 144 | "HOUR", 145 | "MINUTE", 146 | "SECOND", 147 | "NOW", 148 | "AREAS", 149 | "ROWS", 150 | "COLUMNS", 151 | "OFFSET", 152 | "ABSREF", 153 | "RELREF", 154 | "ARGUMENT", 155 | "SEARCH", 156 | "TRANSPOSE", 157 | "ERROR", 158 | "STEP", 159 | "TYPE", 160 | "ECHO", 161 | "SET.NAME", 162 | "CALLER", 163 | "DEREF", 164 | "WINDOWS", 165 | "SERIES", 166 | "DOCUMENTS", 167 | "ACTIVE.CELL", 168 | "SELECTION", 169 | "RESULT", 170 | "ATAN2", 171 | "ASIN", 172 | "ACOS", 173 | "CHOOSE", 174 | "HLOOKUP", 175 | "VLOOKUP", 176 | "LINKS", 177 | "INPUT", 178 | "ISREF", 179 | "GET.FORMULA", 180 | "GET.NAME", 181 | "SET.VALUE", 182 | "LOG", 183 | "EXEC", 184 | "CHAR", 185 | "LOWER", 186 | "UPPER", 187 | "PROPER", 188 | "LEFT", 189 | "RIGHT", 190 | "EXACT", 191 | "TRIM", 192 | "REPLACE", 193 | "SUBSTITUTE", 194 | "CODE", 195 | "NAMES", 196 | "DIRECTORY", 197 | "FIND", 198 | "CELL", 199 | "ISERR", 200 | "ISTEXT", 201 | "ISNUMBER", 202 | "ISBLANK", 203 | "T", 204 | "N", 205 | "FOPEN", 206 | "FCLOSE", 207 | "FSIZE", 208 | "FREADLN", 209 | "FREAD", 210 | "FWRITELN", 211 | "FWRITE", 212 | "FPOS", 213 | "DATEVALUE", 214 | "TIMEVALUE", 215 | "SLN", 216 | "SYD", 217 | "DDB", 218 | "GET.DEF", 219 | "REFTEXT", 220 | "TEXTREF", 221 | "INDIRECT", 222 | "REGISTER", 223 | "CALL", 224 | "ADD.BAR", 225 | "ADD.MENU", 226 | "ADD.COMMAND", 227 | "ENABLE.COMMAND", 228 | "CHECK.COMMAND", 229 | "RENAME.COMMAND", 230 | "SHOW.BAR", 231 | "DELETE.MENU", 232 | "DELETE.COMMAND", 233 | "GET.CHART.ITEM", 234 | "DIALOG.BOX", 235 | "CLEAN", 236 | "MDETERM", 237 | "MINVERSE", 238 | "MMULT", 239 | "FILES", 240 | "IPMT", 241 | "PPMT", 242 | "COUNTA", 243 | "CANCEL.KEY", 244 | "FOR", 245 | "WHILE", 246 | "BREAK", 247 | "NEXT", 248 | "INITIATE", 249 | "REQUEST", 250 | "POKE", 251 | "EXECUTE", 252 | "TERMINATE", 253 | "RESTART", 254 | "HELP", 255 | "GET.BAR", 256 | "PRODUCT", 257 | "FACT", 258 | "GET.CELL", 259 | "GET.WORKSPACE", 260 | "GET.WINDOW", 261 | "GET.DOCUMENT", 262 | "DPRODUCT", 263 | "ISNONTEXT", 264 | "GET.NOTE", 265 | "NOTE", 266 | "STDEVP", 267 | "VARP", 268 | "DSTDEVP", 269 | "DVARP", 270 | "TRUNC", 271 | "ISLOGICAL", 272 | "DCOUNTA", 273 | "DELETE.BAR", 274 | "UNREGISTER", 275 | "", 276 | "", 277 | "USDOLLAR", 278 | "FINDB", 279 | "SEARCHB", 280 | "REPLACEB", 281 | "LEFTB", 282 | "RIGHTB", 283 | "MIDB", 284 | "LENB", 285 | "ROUNDUP", 286 | "ROUNDDOWN", 287 | "ASC", 288 | "DBCS", 289 | "RANK", 290 | "", 291 | "", 292 | "ADDRESS", 293 | "DAYS360", 294 | "TODAY", 295 | "VDB", 296 | "ELSE", 297 | "ELSE.IF", 298 | "END.IF", 299 | "FOR.CELL", 300 | "MEDIAN", 301 | "SUMPRODUCT", 302 | "SINH", 303 | "COSH", 304 | "TANH", 305 | "ASINH", 306 | "ACOSH", 307 | "ATANH", 308 | "DGET", 309 | "CREATE.OBJECT", 310 | "VOLATILE", 311 | "LAST.ERROR", 312 | "CUSTOM.UNDO", 313 | "CUSTOM.REPEAT", 314 | "FORMULA.CONVERT", 315 | "GET.LINK.INFO", 316 | "TEXT.BOX", 317 | "INFO", 318 | "GROUP", 319 | "GET.OBJECT", 320 | "DB", 321 | "PAUSE", 322 | "", 323 | "", 324 | "RESUME", 325 | "FREQUENCY", 326 | "ADD.TOOLBAR", 327 | "DELETE.TOOLBAR", 328 | "User", 329 | "RESET.TOOLBAR", 330 | "EVALUATE", 331 | "GET.TOOLBAR", 332 | "GET.TOOL", 333 | "SPELLING.CHECK", 334 | "ERROR.TYPE", 335 | "APP.TITLE", 336 | "WINDOW.TITLE", 337 | "SAVE.TOOLBAR", 338 | "ENABLE.TOOL", 339 | "PRESS.TOOL", 340 | "REGISTER.ID", 341 | "GET.WORKBOOK", 342 | "AVEDEV", 343 | "BETADIST", 344 | "GAMMALN", 345 | "BETAINV", 346 | "BINOMDIST", 347 | "CHIDIST", 348 | "CHIINV", 349 | "COMBIN", 350 | "CONFIDENCE", 351 | "CRITBINOM", 352 | "EVEN", 353 | "EXPONDIST", 354 | "FDIST", 355 | "FINV", 356 | "FISHER", 357 | "FISHERINV", 358 | "FLOOR", 359 | "GAMMADIST", 360 | "GAMMAINV", 361 | "CEILING", 362 | "HYPGEOMDIST", 363 | "LOGNORMDIST", 364 | "LOGINV", 365 | "NEGBINOMDIST", 366 | "NORMDIST", 367 | "NORMSDIST", 368 | "NORMINV", 369 | "NORMSINV", 370 | "STANDARDIZE", 371 | "ODD", 372 | "PERMUT", 373 | "POISSON", 374 | "TDIST", 375 | "WEIBULL", 376 | "SUMXMY2", 377 | "SUMX2MY2", 378 | "SUMX2PY2", 379 | "CHITEST", 380 | "CORREL", 381 | "COVAR", 382 | "FORECAST", 383 | "FTEST", 384 | "INTERCEPT", 385 | "PEARSON", 386 | "RSQ", 387 | "STEYX", 388 | "SLOPE", 389 | "TTEST", 390 | "PROB", 391 | "DEVSQ", 392 | "GEOMEAN", 393 | "HARMEAN", 394 | "SUMSQ", 395 | "KURT", 396 | "SKEW", 397 | "ZTEST", 398 | "LARGE", 399 | "SMALL", 400 | "QUARTILE", 401 | "PERCENTILE", 402 | "PERCENTRANK", 403 | "MODE", 404 | "TRIMMEAN", 405 | "TINV", 406 | "", 407 | "MOVIE.COMMAND", 408 | "GET.MOVIE", 409 | "CONCATENATE", 410 | "POWER", 411 | "PIVOT.ADD.DATA", 412 | "GET.PIVOT.TABLE", 413 | "GET.PIVOT.FIELD", 414 | "GET.PIVOT.ITEM", 415 | "RADIANS", 416 | "DEGREES", 417 | "SUBTOTAL", 418 | "SUMIF", 419 | "COUNTIF", 420 | "COUNTBLANK", 421 | "SCENARIO.GET", 422 | "OPTIONS.LISTS.GET", 423 | "ISPMT", 424 | "DATEDIF", 425 | "DATESTRING", 426 | "NUMBERSTRING", 427 | "ROMAN", 428 | "OPEN.DIALOG", 429 | "SAVE.DIALOG", 430 | "VIEW.GET", 431 | "GETPIVOTDATA", 432 | "HYPERLINK", 433 | "PHONETIC", 434 | "AVERAGEA", 435 | "MAXA", 436 | "MINA", 437 | "STDEVPA", 438 | "VARPA", 439 | "STDEVA", 440 | "VARA", 441 | "BAHTTEXT", 442 | "THAIDAYOFWEEK", 443 | "THAIDIGIT", 444 | "THAIMONTHOFYEAR", 445 | "THAINUMSOUND", 446 | "THAINUMSTRING", 447 | "THAISTRINGLENGTH", 448 | "ISTHAIDIGIT", 449 | "ROUNDBAHTDOWN", 450 | "ROUNDBAHTUP", 451 | "THAIYEAR", 452 | "RTD", 453 | "CUBEVALUE", 454 | "CUBEMEMBER", 455 | "CUBEMEMBERPROPERTY", 456 | "CUBERANKEDMEMBER", 457 | "HEX2BIN", 458 | "HEX2DEC", 459 | "HEX2OCT", 460 | "DEC2BIN", 461 | "DEC2HEX", 462 | "DEC2OCT", 463 | "OCT2BIN", 464 | "OCT2HEX", 465 | "OCT2DEC", 466 | "BIN2DEC", 467 | "BIN2OCT", 468 | "BIN2HEX", 469 | "IMSUB", 470 | "IMDIV", 471 | "IMPOWER", 472 | "IMABS", 473 | "IMSQRT", 474 | "IMLN", 475 | "IMLOG2", 476 | "IMLOG10", 477 | "IMSIN", 478 | "IMCOS", 479 | "IMEXP", 480 | "IMARGUMENT", 481 | "IMCONJUGATE", 482 | "IMAGINARY", 483 | "IMREAL", 484 | "COMPLEX", 485 | "IMSUM", 486 | "IMPRODUCT", 487 | "SERIESSUM", 488 | "FACTDOUBLE", 489 | "SQRTPI", 490 | "QUOTIENT", 491 | "DELTA", 492 | "GESTEP", 493 | "ISEVEN", 494 | "ISODD", 495 | "MROUND", 496 | "ERF", 497 | "ERFC", 498 | "BESSELJ", 499 | "BESSELK", 500 | "BESSELY", 501 | "BESSELI", 502 | "XIRR", 503 | "XNPV", 504 | "PRICEMAT", 505 | "YIELDMAT", 506 | "INTRATE", 507 | "RECEIVED", 508 | "DISC", 509 | "PRICEDISC", 510 | "YIELDDISC", 511 | "TBILLEQ", 512 | "TBILLPRICE", 513 | "TBILLYIELD", 514 | "PRICE", 515 | "YIELD", 516 | "DOLLARDE", 517 | "DOLLARFR", 518 | "NOMINAL", 519 | "EFFECT", 520 | "CUMPRINC", 521 | "CUMIPMT", 522 | "EDATE", 523 | "EOMONTH", 524 | "YEARFRAC", 525 | "COUPDAYBS", 526 | "COUPDAYS", 527 | "COUPDAYSNC", 528 | "COUPNCD", 529 | "COUPNUM", 530 | "COUPPCD", 531 | "DURATION", 532 | "MDURATION", 533 | "ODDLPRICE", 534 | "ODDLYIELD", 535 | "ODDFPRICE", 536 | "ODDFYIELD", 537 | "RANDBETWEEN", 538 | "WEEKNUM", 539 | "AMORDEGRC", 540 | "AMORLINC", 541 | "CONVERT", 542 | // "SHEETJS", 543 | "ACCRINT", 544 | "ACCRINTM", 545 | "WORKDAY", 546 | "NETWORKDAYS", 547 | "GCD", 548 | "MULTINOMIAL", 549 | "LCM", 550 | "FVSCHEDULE", 551 | "CUBEKPIMEMBER", 552 | "CUBESET", 553 | "CUBESETCOUNT", 554 | "IFERROR", 555 | "COUNTIFS", 556 | "SUMIFS", 557 | "AVERAGEIF", 558 | "AVERAGEIFS", 559 | ]; 560 | 561 | pub const FTAB_ARGC: [u8; FTAB_LEN] = [ 562 | 255, // "COUNT", 563 | 3, // "IF", 564 | 1, // "ISNA", 565 | 1, // "ISERROR", 566 | 255, // "SUM", 567 | 255, // "AVERAGE", 568 | 255, // "MIN", 569 | 255, // "MAX", 570 | 1, // "ROW", 571 | 1, // "COLUMN", 572 | 0, // "NA", 573 | 254, // "NPV", 574 | 255, // "STDEV", 575 | 2, // "DOLLAR", 576 | 3, // "FIXED", 577 | 1, // "SIN", 578 | 1, // "COS", 579 | 1, // "TAN", 580 | 1, // "ATAN", 581 | 0, // "PI", 582 | 1, // "SQRT", 583 | 1, // "EXP", 584 | 1, // "LN", 585 | 1, // "LOG10", 586 | 1, // "ABS", 587 | 1, // "INT", 588 | 1, // "SIGN", 589 | 2, // "ROUND", 590 | 3, // "LOOKUP", 591 | 4, // "INDEX", 592 | 2, // "REPT", 593 | 3, // "MID", 594 | 1, // "LEN", 595 | 1, // "VALUE", 596 | 0, // "TRUE", 597 | 0, // "FALSE", 598 | 255, // "AND", 599 | 255, // "OR", 600 | 1, // "NOT", 601 | 2, // "MOD", 602 | 3, // "DCOUNT", 603 | 3, // "DSUM", 604 | 3, // "DAVERAGE", 605 | 3, // "DMIN", 606 | 3, // "DMAX", 607 | 3, // "DSTDEV", 608 | 255, // "VAR", 609 | 3, // "DVAR", 610 | 2, // "TEXT", 611 | 4, // "LINEST", 612 | 4, // "TREND", 613 | 4, // "LOGEST", 614 | 4, // "GROWTH", 615 | 1, // "GOTO", 616 | 1, // "HALT", 617 | 1, // "RETURN", 618 | 5, // "PV", 619 | 5, // "FV", 620 | 5, // "NPER", 621 | 5, // "PMT", 622 | 6, // "RATE", 623 | 3, // "MIRR", 624 | 2, // "IRR", 625 | 0, // "RAND", 626 | 3, // "MATCH", 627 | 3, // "DATE", 628 | 3, // "TIME", 629 | 1, // "DAY", 630 | 1, // "MONTH", 631 | 1, // "YEAR", 632 | 2, // "WEEKDAY", 633 | 1, // "HOUR", 634 | 1, // "MINUTE", 635 | 1, // "SECOND", 636 | 0, // "NOW", 637 | 1, // "AREAS", 638 | 1, // "ROWS", 639 | 1, // "COLUMNS", 640 | 5, // "OFFSET", 641 | 2, // "ABSREF", 642 | 2, // "RELREF", 643 | 3, // "ARGUMENT", 644 | 3, // "SEARCH", 645 | 1, // "TRANSPOSE", 646 | 2, // "ERROR", 647 | 0, // "STEP", 648 | 1, // "TYPE", 649 | 1, // "ECHO", 650 | 2, // "SET.NAME", 651 | 0, // "CALLER", 652 | 1, // "DEREF", 653 | 2, // "WINDOWS", 654 | 2, // "SERIES", 655 | 2, // "DOCUMENTS", 656 | 0, // "ACTIVE.CELL", 657 | 0, // "SELECTION", 658 | 1, // "RESULT", 659 | 2, // "ATAN2", 660 | 1, // "ASIN", 661 | 1, // "ACOS", 662 | 255, // "CHOOSE", 663 | 4, // "HLOOKUP", 664 | 4, // "VLOOKUP", 665 | 2, // "LINKS", 666 | 7, // "INPUT", 667 | 1, // "ISREF", 668 | 1, // "GET.FORMULA", 669 | 2, // "GET.NAME", 670 | 2, // "SET.VALUE", 671 | 2, // "LOG", 672 | 4, // "EXEC", 673 | 1, // "CHAR", 674 | 1, // "LOWER", 675 | 1, // "UPPER", 676 | 1, // "PROPER", 677 | 2, // "LEFT", 678 | 2, // "RIGHT", 679 | 2, // "EXACT", 680 | 1, // "TRIM", 681 | 4, // "REPLACE", 682 | 4, // "SUBSTITUTE", 683 | 1, // "CODE", 684 | 3, // "NAMES", 685 | 1, // "DIRECTORY", 686 | 3, // "FIND", 687 | 2, // "CELL", 688 | 1, // "ISERR", 689 | 1, // "ISTEXT", 690 | 1, // "ISNUMBER", 691 | 1, // "ISBLANK", 692 | 1, // "T", 693 | 1, // "N", 694 | 2, // "FOPEN", 695 | 1, // "FCLOSE", 696 | 1, // "FSIZE", 697 | 1, // "FREADLN", 698 | 2, // "FREAD", 699 | 2, // "FWRITELN", 700 | 2, // "FWRITE", 701 | 2, // "FPOS", 702 | 1, // "DATEVALUE", 703 | 1, // "TIMEVALUE", 704 | 3, // "SLN", 705 | 4, // "SYD", 706 | 5, // "DDB", 707 | 3, // "GET.DEF", 708 | 2, // "REFTEXT", 709 | 2, // "TEXTREF", 710 | 2, // "INDIRECT", 711 | 255, // "REGISTER", 712 | 255, // "CALL", 713 | 1, // "ADD.BAR", 714 | 4, // "ADD.MENU", 715 | 5, // "ADD.COMMAND", 716 | 5, // "ENABLE.COMMAND", 717 | 5, // "CHECK.COMMAND", 718 | 5, // "RENAME.COMMAND", 719 | 1, // "SHOW.BAR", 720 | 3, // "DELETE.MENU", 721 | 4, // "DELETE.COMMAND", 722 | 3, // "GET.CHART.ITEM", 723 | 1, // "DIALOG.BOX", 724 | 1, // "CLEAN", 725 | 1, // "MDETERM", 726 | 1, // "MINVERSE", 727 | 1, // "MMULT", 728 | 2, // "FILES", 729 | 6, // "IPMT", 730 | 6, // "PPMT", 731 | 255, // "COUNTA", 732 | 2, // "CANCEL.KEY", 733 | 4, // "FOR", 734 | 1, // "WHILE", 735 | 0, // "BREAK", 736 | 0, // "NEXT", 737 | 2, // "INITIATE", 738 | 2, // "REQUEST", 739 | 3, // "POKE", 740 | 2, // "EXECUTE", 741 | 1, // "TERMINATE", 742 | 1, // "RESTART", 743 | 1, // "HELP", 744 | 4, // "GET.BAR", 745 | 255, // "PRODUCT", 746 | 1, // "FACT", 747 | 2, // "GET.CELL", 748 | 1, // "GET.WORKSPACE", 749 | 2, // "GET.WINDOW", 750 | 2, // "GET.DOCUMENT", 751 | 3, // "DPRODUCT", 752 | 1, // "ISNONTEXT", 753 | 3, // "GET.NOTE", 754 | 4, // "NOTE", 755 | 255, // "STDEVP", 756 | 255, // "VARP", 757 | 3, // "DSTDEVP", 758 | 3, // "DVARP", 759 | 2, // "TRUNC", 760 | 1, // "ISLOGICAL", 761 | 3, // "DCOUNTA", 762 | 1, // "DELETE.BAR", 763 | 1, // "UNREGISTER", 764 | 0, // "", 765 | 0, // "", 766 | 2, // "USDOLLAR", 767 | 3, // "FINDB", 768 | 3, // "SEARCHB", 769 | 4, // "REPLACEB", 770 | 2, // "LEFTB", 771 | 2, // "RIGHTB", 772 | 3, // "MIDB", 773 | 3, // "LENB", 774 | 2, // "ROUNDUP", 775 | 2, // "ROUNDDOWN", 776 | 1, // "ASC", 777 | 1, // "DBCS", 778 | 3, // "RANK", 779 | 0, // "", 780 | 0, // "", 781 | 5, // "ADDRESS", 782 | 3, // "DAYS360", 783 | 0, // "TODAY", 784 | 7, // "VDB", 785 | 0, // "ELSE", 786 | 1, // "ELSE.IF", 787 | 0, // "END.IF", 788 | 3, // "FOR.CELL", 789 | 255, // "MEDIAN", 790 | 255, // "SUMPRODUCT", 791 | 1, // "SINH", 792 | 1, // "COSH", 793 | 1, // "TANH", 794 | 1, // "ASINH", 795 | 1, // "ACOSH", 796 | 1, // "ATANH", 797 | 3, // "DGET", 798 | 11, // "CREATE.OBJECT", 799 | 1, // "VOLATILE", 800 | 0, // "LAST.ERROR", 801 | 2, // "CUSTOM.UNDO", 802 | 3, // "CUSTOM.REPEAT", 803 | 5, // "FORMULA.CONVERT", 804 | 4, // "GET.LINK.INFO", 805 | 4, // "TEXT.BOX", 806 | 1, // "INFO", 807 | 0, // "GROUP", 808 | 5, // "GET.OBJECT", 809 | 5, // "DB", 810 | 1, // "PAUSE", 811 | 0, // "", 812 | 0, // "", 813 | 1, // "RESUME", 814 | 2, // "FREQUENCY", 815 | 2, // "ADD.TOOLBAR", 816 | 1, // "DELETE.TOOLBAR", 817 | 255, // "User", 818 | 1, // "RESET.TOOLBAR", 819 | 1, // "EVALUATE", 820 | 2, // "GET.TOOLBAR", 821 | 3, // "GET.TOOL", 822 | 3, // "SPELLING.CHECK", 823 | 1, // "ERROR.TYPE", 824 | 1, // "APP.TITLE", 825 | 1, // "WINDOW.TITLE", 826 | 2, // "SAVE.TOOLBAR", 827 | 3, // "ENABLE.TOOL", 828 | 3, // "PRESS.TOOL", 829 | 3, // "REGISTER.ID", 830 | 2, // "GET.WORKBOOK", 831 | 255, // "AVEDEV", 832 | 5, // "BETADIST", 833 | 1, // "GAMMALN", 834 | 5, // "BETAINV", 835 | 4, // "BINOMDIST", 836 | 2, // "CHIDIST", 837 | 2, // "CHIINV", 838 | 2, // "COMBIN", 839 | 3, // "CONFIDENCE", 840 | 3, // "CRITBINOM", 841 | 1, // "EVEN", 842 | 3, // "EXPONDIST", 843 | 3, // "FDIST", 844 | 3, // "FINV", 845 | 1, // "FISHER", 846 | 1, // "FISHERINV", 847 | 2, // "FLOOR", 848 | 4, // "GAMMADIST", 849 | 3, // "GAMMAINV", 850 | 2, // "CEILING", 851 | 4, // "HYPGEOMDIST", 852 | 3, // "LOGNORMDIST", 853 | 3, // "LOGINV", 854 | 3, // "NEGBINOMDIST", 855 | 4, // "NORMDIST", 856 | 1, // "NORMSDIST", 857 | 3, // "NORMINV", 858 | 1, // "NORMSINV", 859 | 3, // "STANDARDIZE", 860 | 1, // "ODD", 861 | 2, // "PERMUT", 862 | 3, // "POISSON", 863 | 3, // "TDIST", 864 | 4, // "WEIBULL", 865 | 2, // "SUMXMY2", 866 | 2, // "SUMX2MY2", 867 | 2, // "SUMX2PY2", 868 | 2, // "CHITEST", 869 | 2, // "CORREL", 870 | 2, // "COVAR", 871 | 3, // "FORECAST", 872 | 2, // "FTEST", 873 | 2, // "INTERCEPT", 874 | 2, // "PEARSON", 875 | 2, // "RSQ", 876 | 2, // "STEYX", 877 | 2, // "SLOPE", 878 | 4, // "TTEST", 879 | 4, // "PROB", 880 | 255, // "DEVSQ", 881 | 255, // "GEOMEAN", 882 | 255, // "HARMEAN", 883 | 255, // "SUMSQ", 884 | 255, // "KURT", 885 | 255, // "SKEW", 886 | 3, // "ZTEST", 887 | 2, // "LARGE", 888 | 2, // "SMALL", 889 | 2, // "QUARTILE", 890 | 2, // "PERCENTILE", 891 | 3, // "PERCENTRANK", 892 | 255, // "MODE", 893 | 2, // "TRIMMEAN", 894 | 2, // "TINV", 895 | 4, // "", 896 | 4, // "MOVIE.COMMAND", 897 | 3, // "GET.MOVIE", 898 | 255, // "CONCATENATE", 899 | 2, // "POWER", 900 | 9, // "PIVOT.ADD.DATA", 901 | 2, // "GET.PIVOT.TABLE", 902 | 3, // "GET.PIVOT.FIELD", 903 | 4, // "GET.PIVOT.ITEM", 904 | 1, // "RADIANS", 905 | 1, // "DEGREES", 906 | 255, // "SUBTOTAL", 907 | 3, // "SUMIF", 908 | 2, // "COUNTIF", 909 | 1, // "COUNTBLANK", 910 | 2, // "SCENARIO.GET", 911 | 1, // "OPTIONS.LISTS.GET", 912 | 4, // "ISPMT", 913 | 3, // "DATEDIF", 914 | 1, // "DATESTRING", 915 | 2, // "NUMBERSTRING", 916 | 2, // "ROMAN", 917 | 4, // "OPEN.DIALOG", 918 | 5, // "SAVE.DIALOG", 919 | 2, // "VIEW.GET", 920 | 128, // "GETPIVOTDATA", 921 | 2, // "HYPERLINK", 922 | 1, // "PHONETIC", 923 | 255, // "AVERAGEA", 924 | 255, // "MAXA", 925 | 255, // "MINA", 926 | 255, // "STDEVPA", 927 | 255, // "VARPA", 928 | 255, // "STDEVA", 929 | 255, // "VARA", 930 | 1, // "BAHTTEXT", 931 | 1, // "THAIDAYOFWEEK", 932 | 1, // "THAIDIGIT", 933 | 1, // "THAIMONTHOFYEAR", 934 | 1, // "THAINUMSOUND", 935 | 1, // "THAINUMSTRING", 936 | 1, // "THAISTRINGLENGTH", 937 | 1, // "ISTHAIDIGIT", 938 | 1, // "ROUNDBAHTDOWN", 939 | 1, // "ROUNDBAHTUP", 940 | 1, // "THAIYEAR", 941 | 255, // "RTD", 942 | 255, // "CUBEVALUE", 943 | 3, // "CUBEMEMBER", 944 | 3, // "CUBEMEMBERPROPERTY", 945 | 4, // "CUBERANKEDMEMBER", 946 | 2, // "HEX2BIN", 947 | 1, // "HEX2DEC", 948 | 2, // "HEX2OCT", 949 | 2, // "DEC2BIN", 950 | 2, // "DEC2HEX", 951 | 2, // "DEC2OCT", 952 | 2, // "OCT2BIN", 953 | 2, // "OCT2HEX", 954 | 1, // "OCT2DEC", 955 | 1, // "BIN2DEC", 956 | 2, // "BIN2OCT", 957 | 2, // "BIN2HEX", 958 | 2, // "IMSUB", 959 | 2, // "IMDIV", 960 | 2, // "IMPOWER", 961 | 1, // "IMABS", 962 | 1, // "IMSQRT", 963 | 1, // "IMLN", 964 | 1, // "IMLOG2", 965 | 1, // "IMLOG10", 966 | 1, // "IMSIN", 967 | 1, // "IMCOS", 968 | 1, // "IMEXP", 969 | 1, // "IMARGUMENT", 970 | 1, // "IMCONJUGATE", 971 | 1, // "IMAGINARY", 972 | 1, // "IMREAL", 973 | 3, // "COMPLEX", 974 | 255, // "IMSUM", 975 | 255, // "IMPRODUCT", 976 | 4, // "SERIESSUM", 977 | 1, // "FACTDOUBLE", 978 | 1, // "SQRTPI", 979 | 2, // "QUOTIENT", 980 | 2, // "DELTA", 981 | 2, // "GESTEP", 982 | 1, // "ISEVEN", 983 | 1, // "ISODD", 984 | 2, // "MROUND", 985 | 2, // "ERF", 986 | 1, // "ERFC", 987 | 2, // "BESSELJ", 988 | 2, // "BESSELK", 989 | 2, // "BESSELY", 990 | 2, // "BESSELI", 991 | 3, // "XIRR", 992 | 3, // "XNPV", 993 | 6, // "PRICEMAT", 994 | 6, // "YIELDMAT", 995 | 5, // "INTRATE", 996 | 5, // "RECEIVED", 997 | 5, // "DISC", 998 | 5, // "PRICEDISC", 999 | 5, // "YIELDDISC", 1000 | 3, // "TBILLEQ", 1001 | 3, // "TBILLPRICE", 1002 | 3, // "TBILLYIELD", 1003 | 7, // "PRICE", 1004 | 7, // "YIELD", 1005 | 2, // "DOLLARDE", 1006 | 2, // "DOLLARFR", 1007 | 2, // "NOMINAL", 1008 | 2, // "EFFECT", 1009 | 6, // "CUMPRINC", 1010 | 6, // "CUMIPMT", 1011 | 2, // "EDATE", 1012 | 2, // "EOMONTH", 1013 | 3, // "YEARFRAC", 1014 | 4, // "COUPDAYBS", 1015 | 4, // "COUPDAYS", 1016 | 4, // "COUPDAYSNC", 1017 | 4, // "COUPNCD", 1018 | 4, // "COUPNUM", 1019 | 4, // "COUPPCD", 1020 | 6, // "DURATION", 1021 | 6, // "MDURATION", 1022 | 8, // "ODDLPRICE", 1023 | 8, // "ODDLYIELD", 1024 | 8, // "ODDFPRICE", 1025 | 8, // "ODDFYIELD", 1026 | 2, // "RANDBETWEEN", 1027 | 2, // "WEEKNUM", 1028 | 7, // "AMORDEGRC", 1029 | 7, // "AMORLINC", 1030 | 8, // "CONVERT", 1031 | // 1, // "SHEETJS", 1032 | 8, // "ACCRINT", 1033 | 5, // "ACCRINTM", 1034 | 3, // "WORKDAY", 1035 | 3, // "NETWORKDAYS", 1036 | 255, // "GCD", 1037 | 255, // "MULTINOMIAL", 1038 | 255, // "LCM", 1039 | 2, // "FVSCHEDULE", 1040 | 4, // "CUBEKPIMEMBER", 1041 | 5, // "CUBESET", 1042 | 1, // "CUBESETCOUNT", 1043 | 2, // "IFERROR", 1044 | 128, // "COUNTIFS", 1045 | 129, // "SUMIFS", 1046 | 3, // "AVERAGEIF", 1047 | 129, // "AVERAGEIFS" 1048 | ]; 1049 | 1050 | #[cfg(test)] 1051 | mod tests { 1052 | use super::*; 1053 | 1054 | #[test] 1055 | fn sound_to_u32() { 1056 | let data = b"ABCDEFGH"; 1057 | assert_eq!( 1058 | to_u32(data).collect::>(), 1059 | [u32::from_le_bytes(*b"ABCD"), u32::from_le_bytes(*b"EFGH")] 1060 | ); 1061 | } 1062 | } 1063 | -------------------------------------------------------------------------------- /@rsw/excel-read/calamine/src/vba.rs: -------------------------------------------------------------------------------- 1 | //! Parse vbaProject.bin file 2 | //! 3 | //! Retranscription from: 4 | //! https://github.com/unixfreak0037/officeparser/blob/master/officeparser.py 5 | 6 | use std::collections::HashMap; 7 | use std::io::Read; 8 | use std::path::PathBuf; 9 | 10 | use byteorder::{LittleEndian, ReadBytesExt}; 11 | use log::{debug, log_enabled, warn, Level}; 12 | 13 | use crate::cfb::{Cfb, XlsEncoding}; 14 | use crate::utils::read_u16; 15 | 16 | /// A VBA specific error enum 17 | #[derive(Debug)] 18 | pub enum VbaError { 19 | /// Error comes from a cfb parsing 20 | Cfb(crate::cfb::CfbError), 21 | /// Io error 22 | Io(std::io::Error), 23 | 24 | /// Cannot find module 25 | ModuleNotFound(String), 26 | /// Generic unknown u16 value 27 | Unknown { 28 | /// error type 29 | typ: &'static str, 30 | /// value found 31 | val: u16, 32 | }, 33 | /// Invalid libid format 34 | LibId, 35 | /// Invalid record id 36 | InvalidRecordId { 37 | /// expected record id 38 | expected: u16, 39 | /// record if found 40 | found: u16, 41 | }, 42 | } 43 | 44 | from_err!(crate::cfb::CfbError, VbaError, Cfb); 45 | from_err!(std::io::Error, VbaError, Io); 46 | 47 | impl std::fmt::Display for VbaError { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | match self { 50 | VbaError::Io(e) => write!(f, "I/O error: {}", e), 51 | VbaError::Cfb(e) => write!(f, "Cfb error: {}", e), 52 | 53 | VbaError::ModuleNotFound(e) => write!(f, "Cannot find module '{}'", e), 54 | VbaError::Unknown { typ, val } => write!(f, "Unknown {} '{:X}'", typ, val), 55 | VbaError::LibId => write!(f, "Unexpected libid format"), 56 | VbaError::InvalidRecordId { expected, found } => write!( 57 | f, 58 | "Invalid record id: expecting {:X} found {:X}", 59 | expected, found 60 | ), 61 | } 62 | } 63 | } 64 | 65 | impl std::error::Error for VbaError { 66 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 67 | match self { 68 | VbaError::Io(e) => Some(e), 69 | VbaError::Cfb(e) => Some(e), 70 | _ => None, 71 | } 72 | } 73 | } 74 | 75 | /// A struct for managing VBA reading 76 | #[allow(dead_code)] 77 | #[derive(Clone)] 78 | pub struct VbaProject { 79 | references: Vec, 80 | modules: HashMap>, 81 | encoding: XlsEncoding, 82 | } 83 | 84 | impl VbaProject { 85 | /// Create a new `VbaProject` out of the vbaProject.bin `ZipFile` or xls file 86 | /// 87 | /// Starts reading project metadata (header, directories, sectors and minisectors). 88 | pub fn new(r: &mut R, len: usize) -> Result { 89 | let mut cfb = Cfb::new(r, len)?; 90 | VbaProject::from_cfb(r, &mut cfb) 91 | } 92 | 93 | /// Creates a new `VbaProject` out of a Compound File Binary and the corresponding reader 94 | pub fn from_cfb(r: &mut R, cfb: &mut Cfb) -> Result { 95 | // dir stream 96 | let stream = cfb.get_stream("dir", r)?; 97 | let stream = crate::cfb::decompress_stream(&*stream)?; 98 | let stream = &mut &*stream; 99 | 100 | // read dir information record (not used) 101 | let encoding = read_dir_information(stream)?; 102 | 103 | // array of REFERENCE records 104 | let refs = Reference::from_stream(stream, &encoding)?; 105 | 106 | // modules 107 | let mods: Vec = read_modules(stream, &encoding)?; 108 | 109 | // read all modules 110 | let modules: HashMap> = mods 111 | .into_iter() 112 | .map(|m| { 113 | cfb.get_stream(&m.stream_name, r).and_then(|s| { 114 | crate::cfb::decompress_stream(&s[m.text_offset..]).map(move |s| (m.name, s)) 115 | }) 116 | }) 117 | .collect::, _>>()?; 118 | 119 | Ok(VbaProject { 120 | references: refs, 121 | modules, 122 | encoding, 123 | }) 124 | } 125 | 126 | /// Gets the list of `Reference`s 127 | pub fn get_references(&self) -> &[Reference] { 128 | &self.references 129 | } 130 | 131 | /// Gets the list of `Module` names 132 | pub fn get_module_names(&self) -> Vec<&str> { 133 | self.modules.keys().map(|k| &**k).collect() 134 | } 135 | 136 | /// Reads module content and tries to convert to utf8 137 | /// 138 | /// While it works most of the time, the modules are MBCS encoding and the conversion 139 | /// may fail. If this is the case you should revert to `read_module_raw` as there is 140 | /// no built in decoding provided in this crate 141 | /// 142 | /// # Examples 143 | /// ``` 144 | /// use calamine::{Reader, open_workbook, Xlsx}; 145 | /// 146 | /// # let path = format!("{}/tests/vba.xlsm", env!("CARGO_MANIFEST_DIR")); 147 | /// let mut xl: Xlsx<_> = open_workbook(path).expect("Cannot find excel file"); 148 | /// if let Some(Ok(mut vba)) = xl.vba_project() { 149 | /// let vba = vba.to_mut(); 150 | /// let modules = vba.get_module_names().into_iter() 151 | /// .map(|s| s.to_string()).collect::>(); 152 | /// for m in modules { 153 | /// println!("Module {}:", m); 154 | /// println!("{}", vba.get_module(&m) 155 | /// .expect(&format!("cannot read {:?} module", m))); 156 | /// } 157 | /// } 158 | /// ``` 159 | pub fn get_module(&self, name: &str) -> Result { 160 | debug!("read module {}", name); 161 | let data = self.get_module_raw(name)?; 162 | Ok(self.encoding.decode_all(data, None)) 163 | } 164 | 165 | /// Reads module content (MBCS encoded) and output it as-is (binary output) 166 | pub fn get_module_raw(&self, name: &str) -> Result<&[u8], VbaError> { 167 | match self.modules.get(name) { 168 | Some(m) => Ok(&**m), 169 | None => Err(VbaError::ModuleNotFound(name.into())), 170 | } 171 | } 172 | } 173 | 174 | /// A vba reference 175 | #[derive(Debug, Clone, Hash, Eq, PartialEq)] 176 | pub struct Reference { 177 | /// name 178 | pub name: String, 179 | /// description 180 | pub description: String, 181 | /// location of the reference 182 | pub path: PathBuf, 183 | } 184 | 185 | impl Reference { 186 | /// Check if the reference location is accessible 187 | pub fn is_missing(&self) -> bool { 188 | !self.path.exists() 189 | } 190 | 191 | /// Gets the list of references from the dir_stream relevant part 192 | fn from_stream(stream: &mut &[u8], encoding: &XlsEncoding) -> Result, VbaError> { 193 | debug!("read all references metadata"); 194 | 195 | let mut references = Vec::new(); 196 | let mut reference = Reference { 197 | name: "".to_string(), 198 | description: "".to_string(), 199 | path: "".into(), 200 | }; 201 | 202 | loop { 203 | let check = stream.read_u16::(); 204 | match check? { 205 | 0x000F => { 206 | // termination of references array 207 | if !reference.name.is_empty() { 208 | references.push(reference); 209 | } 210 | break; 211 | } 212 | 0x0016 => { 213 | // REFERENCENAME 214 | if !reference.name.is_empty() { 215 | references.push(reference); 216 | } 217 | let name = read_variable_record(stream, 1)?; 218 | let name = encoding.decode_all(name, None); 219 | reference = Reference { 220 | name: name.clone(), 221 | description: name, 222 | path: "".into(), 223 | }; 224 | check_variable_record(0x003E, stream)?; // unicode 225 | } 226 | 0x0033 => { 227 | // REFERENCEORIGINAL (followed by REFERENCECONTROL) 228 | reference.set_libid(stream, encoding)?; 229 | } 230 | 0x002F => { 231 | // REFERENCECONTROL 232 | *stream = &stream[4..]; // SizeTwiddled: len of total ref control 233 | reference.set_libid(stream, encoding)?; 234 | 235 | *stream = &stream[6..]; 236 | match stream.read_u16::()? { 237 | 0x0016 => { 238 | // optional name record extended 239 | read_variable_record(stream, 1)?; // name extended 240 | check_variable_record(0x003E, stream)?; // name extended unicode 241 | check_record(0x0030, stream)?; 242 | } 243 | 0x0030 => (), 244 | e => { 245 | return Err(VbaError::Unknown { 246 | typ: "token in reference control", 247 | val: e, 248 | }); 249 | } 250 | } 251 | *stream = &stream[4..]; 252 | reference.set_libid(stream, encoding)?; 253 | *stream = &stream[26..]; 254 | } 255 | 0x000D => { 256 | // REFERENCEREGISTERED 257 | *stream = &stream[4..]; 258 | reference.set_libid(stream, encoding)?; 259 | *stream = &stream[6..]; 260 | } 261 | 0x000E => { 262 | // REFERENCEPROJECT 263 | *stream = &stream[4..]; 264 | let absolute = read_variable_record(stream, 1)?; // project libid absolute 265 | { 266 | let absolute = encoding.decode_all(absolute, None); 267 | reference.path = if absolute.starts_with("*\\C") { 268 | absolute[3..].into() 269 | } else { 270 | absolute.into() 271 | }; 272 | } 273 | read_variable_record(stream, 1)?; // project libid relative 274 | *stream = &stream[6..]; 275 | } 276 | c => { 277 | return Err(VbaError::Unknown { 278 | typ: "check id", 279 | val: c, 280 | }); 281 | } 282 | } 283 | } 284 | 285 | debug!("references: {:#?}", references); 286 | Ok(references) 287 | } 288 | 289 | fn set_libid(&mut self, stream: &mut &[u8], encoding: &XlsEncoding) -> Result<(), VbaError> { 290 | let libid = read_variable_record(stream, 1)?; //libid twiddled 291 | if libid.is_empty() || libid.ends_with(b"##") { 292 | return Ok(()); 293 | } 294 | let libid = encoding.decode_all(libid, None); 295 | let mut parts = libid.rsplit('#'); 296 | match (parts.next(), parts.next()) { 297 | (Some(desc), Some(path)) => { 298 | self.description = desc.into(); 299 | // use original path if already set 300 | if !path.is_empty() && self.path.as_os_str().is_empty() { 301 | self.path = path.into(); 302 | } 303 | Ok(()) 304 | } 305 | _ => Err(VbaError::LibId), 306 | } 307 | } 308 | } 309 | 310 | /// A vba module 311 | #[derive(Debug, Clone, Default)] 312 | struct Module { 313 | /// module name as it appears in vba project 314 | name: String, 315 | stream_name: String, 316 | text_offset: usize, 317 | } 318 | 319 | fn read_dir_information(stream: &mut &[u8]) -> Result { 320 | debug!("read dir header"); 321 | 322 | // PROJECTSYSKIND, PROJECTLCID and PROJECTLCIDINVOKE Records 323 | *stream = &stream[30..]; 324 | 325 | // PROJECT Codepage 326 | let encoding = XlsEncoding::from_codepage(read_u16(&stream[6..8]))?; 327 | *stream = &stream[8..]; 328 | 329 | // PROJECTNAME Record 330 | check_variable_record(0x0004, stream)?; 331 | 332 | // PROJECTDOCSTRING Record 333 | check_variable_record(0x0005, stream)?; 334 | check_variable_record(0x0040, stream)?; // unicode 335 | 336 | // PROJECTHELPFILEPATH Record - MS-OVBA 2.3.4.2.1.7 337 | check_variable_record(0x0006, stream)?; 338 | check_variable_record(0x003D, stream)?; 339 | 340 | // PROJECTHELPCONTEXT PROJECTLIBFLAGS and PROJECTVERSION Records 341 | *stream = &stream[32..]; 342 | 343 | // PROJECTCONSTANTS Record 344 | check_variable_record(0x000C, stream)?; 345 | check_variable_record(0x003C, stream)?; // unicode 346 | 347 | Ok(encoding) 348 | } 349 | 350 | fn read_modules(stream: &mut &[u8], encoding: &XlsEncoding) -> Result, VbaError> { 351 | debug!("read all modules metadata"); 352 | *stream = &stream[4..]; 353 | 354 | let module_len = stream.read_u16::()? as usize; 355 | 356 | *stream = &stream[8..]; // PROJECTCOOKIE record 357 | let mut modules = Vec::with_capacity(module_len); 358 | 359 | for _ in 0..module_len { 360 | // name 361 | let name = check_variable_record(0x0019, stream)?; 362 | let name = encoding.decode_all(name, None); 363 | 364 | check_variable_record(0x0047, stream)?; // unicode 365 | 366 | let stream_name = check_variable_record(0x001A, stream)?; // stream name 367 | let stream_name = encoding.decode_all(stream_name, None); 368 | 369 | check_variable_record(0x0032, stream)?; // stream name unicode 370 | check_variable_record(0x001C, stream)?; // doc string 371 | check_variable_record(0x0048, stream)?; // doc string unicode 372 | 373 | // offset 374 | check_record(0x0031, stream)?; 375 | *stream = &stream[4..]; 376 | let offset = stream.read_u32::()? as usize; 377 | 378 | // help context 379 | check_record(0x001E, stream)?; 380 | *stream = &stream[8..]; 381 | 382 | // cookie 383 | check_record(0x002C, stream)?; 384 | *stream = &stream[6..]; 385 | 386 | match stream.read_u16::()? { 387 | 0x0021 /* procedural module */ | 388 | 0x0022 /* document, class or designer module */ => (), 389 | e => return Err(VbaError::Unknown { typ: "module typ", val: e }), 390 | } 391 | 392 | loop { 393 | *stream = &stream[4..]; // reserved 394 | match stream.read_u16::() { 395 | Ok(0x0025) /* readonly */ | Ok(0x0028) /* private */ => (), 396 | Ok(0x002B) => break, 397 | Ok(e) => return Err(VbaError::Unknown { typ: "record id", val: e }), 398 | Err(e) => return Err(VbaError::Io(e)), 399 | } 400 | } 401 | *stream = &stream[4..]; // reserved 402 | 403 | modules.push(Module { 404 | name, 405 | stream_name, 406 | text_offset: offset, 407 | }); 408 | } 409 | 410 | Ok(modules) 411 | } 412 | 413 | /// Reads a variable length record 414 | /// 415 | /// `mult` is a multiplier of the length (e.g 2 when parsing XLWideString) 416 | fn read_variable_record<'a>(r: &mut &'a [u8], mult: usize) -> Result<&'a [u8], VbaError> { 417 | let len = r.read_u32::()? as usize * mult; 418 | let (read, next) = r.split_at(len); 419 | *r = next; 420 | Ok(read) 421 | } 422 | 423 | /// Check that next record matches `id` and returns a variable length record 424 | fn check_variable_record<'a>(id: u16, r: &mut &'a [u8]) -> Result<&'a [u8], VbaError> { 425 | check_record(id, r)?; 426 | let record = read_variable_record(r, 1)?; 427 | if log_enabled!(Level::Warn) && record.len() > 100_000 { 428 | warn!( 429 | "record id {} as a suspicious huge length of {} (hex: {:x})", 430 | id, 431 | record.len(), 432 | record.len() as u32 433 | ); 434 | } 435 | Ok(record) 436 | } 437 | 438 | /// Check that next record matches `id` 439 | fn check_record(id: u16, r: &mut &[u8]) -> Result<(), VbaError> { 440 | debug!("check record {:x}", id); 441 | let record_id = r.read_u16::()?; 442 | if record_id != id { 443 | Err(VbaError::InvalidRecordId { 444 | expected: id, 445 | found: record_id, 446 | }) 447 | } else { 448 | Ok(()) 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /@rsw/excel-read/src/excel.rs: -------------------------------------------------------------------------------- 1 | use crate::JsonV; 2 | use calamine::{DataType, Reader, Sheets}; 3 | use wasm_bindgen::JsValue; 4 | 5 | /// sheets: excel对象 6 | /// title_row: 标题在多少行 7 | /// rows_excluded: 数据排除多少行 8 | pub fn run( 9 | mut sheets: Sheets, 10 | title_row: Vec, 11 | rows_excluded: Vec, 12 | excluded_keyword: String, 13 | ) -> Result { 14 | let sheet_names = sheets.sheet_names().to_vec(); 15 | let r: js_sys::Map = js_sys::Map::new(); 16 | for name in &sheet_names { 17 | let r_arr = js_sys::Array::new(); 18 | let mut titles = vec![]; 19 | 20 | #[allow(unused_assignments)] 21 | let mut tt: Vec = vec![]; 22 | 23 | let sheet = match sheets.worksheet_range(name) { 24 | Some(Ok(v)) => v, 25 | e => Err(format!("sheet_name[{}]有误: {:?}", name, e))?, 26 | }; 27 | 28 | // 处理标题 29 | { 30 | // 获得所有标题 31 | let mut title_count = title_row.len(); 32 | let mut row_count = 1_usize; 33 | for row in sheet.rows() { 34 | if title_count == 0 { 35 | break; 36 | } 37 | 38 | if title_row.contains(&row_count) { 39 | title_count -= 1; 40 | let mut titles2 = vec![]; 41 | for i in 0..row.len() { 42 | let item = &row[i]; 43 | let v = item.get_string().unwrap_or_default(); 44 | if v.trim() != "" { 45 | titles2.push(JsonV::String(v.to_string())); 46 | } else { 47 | titles2.push(JsonV::Null); 48 | } 49 | } 50 | titles.push(titles2); 51 | } 52 | row_count += 1; 53 | } 54 | 55 | if titles.len() > 0 { 56 | tt = titles[titles.len() - 1].clone(); 57 | } else { 58 | continue; 59 | } 60 | 61 | // 行数 62 | let row_num = titles.len(); 63 | 64 | // 把值往下掉 65 | for i in 1..row_num { 66 | for j in 0..titles[i].len() { 67 | if titles[i][j].is_null() && !titles[i - 1][j].is_null() { 68 | titles[i][j] = titles[i - 1][j].clone(); 69 | } 70 | } 71 | } 72 | 73 | // 列数 74 | let col_num = titles[titles.len() - 1].len(); 75 | 76 | // crate::log(format!("tt: {:?}", tt).into()); 77 | 78 | // 查找 79 | let mut find = |col_i: usize| { 80 | // 往上面的循环: 标题 81 | for row_ii in (0..row_num - 1).rev() { 82 | // 往左循环: 列 83 | for col_ii in (0..=col_i).rev() { 84 | let v = titles[row_ii][col_ii].as_str().unwrap_or_default(); 85 | if v != "" { 86 | let t = tt[col_i].as_str().unwrap_or_default().to_string(); 87 | tt[col_i] = if t == "" || t == v { 88 | json!(v) 89 | } else { 90 | json!(format!("{}:{}", v, t)) 91 | }; 92 | break; 93 | } 94 | } 95 | } 96 | }; 97 | 98 | // 循环列索引 99 | for col_i in 0..col_num { 100 | find(col_i); 101 | } 102 | } 103 | 104 | // 处理数据 105 | { 106 | let rows = sheet.rows(); 107 | let mut row_i = 0_usize; 108 | 109 | // 循环每一行数据 110 | 'lable: for row in rows { 111 | row_i += 1; 112 | 113 | if title_row.contains(&row_i) || rows_excluded.contains(&row_i) { 114 | continue; 115 | } 116 | 117 | let mut have_data = false; 118 | let m: js_sys::Map = js_sys::Map::new(); 119 | 120 | // 循环每一列数据 121 | for i in 0..tt.len() { 122 | let v = row.get(i).unwrap_or_else(|| &DataType::Empty); 123 | let v: JsValue = match v { 124 | calamine::DataType::Float(v) => JsValue::from(*v), 125 | calamine::DataType::Int(v) => JsValue::from(*v as i32), 126 | v => { 127 | let v = v.to_string(); 128 | if excluded_keyword != "" && v.replace(' ', "") == excluded_keyword { 129 | break 'lable; 130 | } 131 | if v.trim() == "" { 132 | JsValue::NULL 133 | } else { 134 | v.into() 135 | } 136 | } 137 | }; 138 | 139 | if v != JsValue::NULL { 140 | have_data = true; 141 | } 142 | 143 | m.set(&JsValue::from(tt[i].as_str()), &v); 144 | } 145 | if !have_data { 146 | break 'lable; 147 | } 148 | 149 | let m = js_sys::Object::from_entries(&m.into())?; 150 | r_arr.push(&m); 151 | } 152 | r.set(&JsValue::from(name.as_str()), &r_arr.into()); 153 | } 154 | } 155 | 156 | let r = js_sys::Object::from_entries(&r.into())?; 157 | Ok(r.into()) 158 | } -------------------------------------------------------------------------------- /@rsw/excel-read/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod excel; 2 | // mod utils; 3 | 4 | use serde_json::Value as JsonV; 5 | use wasm_bindgen::prelude::*; 6 | use wasm_bindgen_futures::JsFuture; 7 | use web_sys::Blob; 8 | 9 | #[macro_use] 10 | extern crate serde_json; 11 | 12 | // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global 13 | // allocator. 14 | #[cfg(feature = "wee_alloc")] 15 | #[global_allocator] 16 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 17 | 18 | #[wasm_bindgen] 19 | extern "C" { 20 | #[wasm_bindgen(js_namespace = console)] 21 | fn log(s: JsValue); 22 | #[wasm_bindgen(js_namespace = console)] 23 | fn error(s: &str); 24 | fn alert(s: &str); 25 | } 26 | 27 | #[wasm_bindgen(start)] 28 | pub fn run() { 29 | log("wasm start".into()); 30 | } 31 | 32 | #[wasm_bindgen] 33 | pub fn greet() { 34 | alert("Hello, excel_read!"); 35 | } 36 | 37 | /// 38 | /// file: 前端File对象 39 | /// title_row: 标题在第几行 组合标题: [2,3] 40 | /// rows_excluded: 排除多少行数据, 一行, 二行, 三行: [1,2,3] 41 | /// excluded_keyword: 关键字排除: 在单元格中检测到该关键字读取终止 42 | /// 43 | #[wasm_bindgen] 44 | pub async fn read_excel_file( 45 | file: web_sys::File, 46 | title_row: wasm_bindgen::JsValue, 47 | rows_excluded: wasm_bindgen::JsValue, 48 | excluded_keyword: JsValue, 49 | ) -> Result { 50 | // 获得标题在第几栏 51 | let title_row = title_row 52 | .into_serde::>() 53 | .map_err(|e| JsValue::from(format!("title_row into_serde error: {}", e)))?; 54 | 55 | // 排除第几行数据 56 | let rows_excluded = rows_excluded 57 | .into_serde::>() 58 | .map_err(|e| JsValue::from(format!("rows_excluded into_serde error: {}", e)))?; 59 | 60 | // 怱略的关键字 61 | let excluded_keyword = excluded_keyword.as_string().unwrap_or_default(); 62 | 63 | // 解析文件名 64 | let file_name: String = file.name(); 65 | let file_name = file_name.to_lowercase(); 66 | 67 | // 获得文件名后缀 68 | let suffix = if file_name.ends_with("xls") { 69 | "xls".to_owned() 70 | } else if file_name.ends_with("xlsx") { 71 | "xlsx".to_owned() 72 | } else { 73 | Err("当前不是一个excel文件")? 74 | }; 75 | 76 | // 读取文件二进制流 77 | let buffer: Blob = file.slice()?; 78 | let buffer: JsValue = JsFuture::from(buffer.array_buffer()).await?; 79 | let buffer: Vec = js_sys::Uint8Array::new(&buffer).to_vec(); 80 | // log(buffer.len().to_string().into()); 81 | 82 | // 打开excel 83 | let sheets = calamine::open_workbook_auto_buff(buffer, &suffix) 84 | .map_err(|e| JsValue::from_str(&format!("Xlsx-error: {:?}", e)))?; 85 | 86 | // 解析excel 87 | let r = excel::run(sheets, title_row, rows_excluded, excluded_keyword)?; 88 | 89 | // 把json数据转型为Js的数据类型 90 | // let r = 91 | // JsValue::from_serde(&r).map_err(|e| JsValue::from(format!("from_serde error: {}", e)))?; 92 | 93 | // 返回数据 94 | Ok(r) 95 | } -------------------------------------------------------------------------------- /@rsw/excel-read/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } -------------------------------------------------------------------------------- /@rsw/excel-read/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /@rsw/fractals/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fractals" 3 | version = "0.1.0" 4 | authors = ["lencx "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | image = "0.23.14" 11 | 12 | [dependencies.num-complex] 13 | version = "0.4" 14 | default-features = false -------------------------------------------------------------------------------- /@rsw/fractals/src/main.rs: -------------------------------------------------------------------------------- 1 | //! An example of generating julia fractals. 2 | extern crate image; 3 | extern crate num_complex; 4 | 5 | fn main() { 6 | let imgx = 800; 7 | let imgy = 800; 8 | 9 | let scalex = 3.0 / imgx as f32; 10 | let scaley = 3.0 / imgy as f32; 11 | 12 | // Create a new ImgBuf with width: imgx and height: imgy 13 | let mut imgbuf = image::ImageBuffer::new(imgx, imgy); 14 | 15 | // Iterate over the coordinates and pixels of the image 16 | for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { 17 | let r = (0.3 * x as f32) as u8; 18 | let b = (0.3 * y as f32) as u8; 19 | *pixel = image::Rgb([r, 0, b]); 20 | } 21 | 22 | // A redundant loop to demonstrate reading image data 23 | for x in 0..imgx { 24 | for y in 0..imgy { 25 | let cx = y as f32 * scalex - 1.5; 26 | let cy = x as f32 * scaley - 1.5; 27 | 28 | let c = num_complex::Complex::new(-0.4, 0.6); 29 | let mut z = num_complex::Complex::new(cx, cy); 30 | 31 | let mut i = 0; 32 | while i < 255 && z.norm_sqr() <= 2.0 { 33 | z = z * z + c; 34 | i += 1; 35 | } 36 | 37 | let pixel = imgbuf.get_pixel_mut(x, y); 38 | let image::Rgb(data) = *pixel; 39 | *pixel = image::Rgb([data[0], i as u8, data[2]]); 40 | } 41 | } 42 | 43 | // Save the image as “fractal.png”, the format is deduced from the path 44 | imgbuf.save("fractal.png").unwrap(); 45 | } -------------------------------------------------------------------------------- /@rsw/game-of-life/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "game-of-life" 3 | version = "0.1.0" 4 | authors = ["lencx "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | wasm-bindgen = "0.2.71" 13 | fixedbitset = "0.4.0" 14 | js-sys = "0.3.48" -------------------------------------------------------------------------------- /@rsw/game-of-life/src/lib.rs: -------------------------------------------------------------------------------- 1 | // https://rustwasm.github.io/docs/book/game-of-life/hello-world.html 2 | 3 | extern crate js_sys; 4 | use wasm_bindgen::prelude::*; 5 | use fixedbitset::FixedBitSet; 6 | 7 | // 定义宇宙Universe 8 | #[wasm_bindgen] 9 | pub struct Universe { 10 | width: u32, // 宇宙宽度 11 | height: u32, // 宇宙高度 12 | // 存活 - ◼:1 13 | // 死亡 - ◻:0 14 | cells: FixedBitSet, // 宇宙内的细胞集合 15 | } 16 | 17 | impl Universe { 18 | // 获取给定行列处的细胞索引 19 | fn get_index(&self, row: u32, column: u32) -> usize { 20 | (row * self.width + column) as usize 21 | } 22 | 23 | // 为了计算细胞的下一个状态 24 | // 需要先获得一个还有多少邻居存活的计数 25 | fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { 26 | let mut count = 0; 27 | for delta_row in [self.height - 1, 0, 1].iter().cloned() { 28 | for delta_col in [self.width - 1, 0, 1].iter().cloned() { 29 | if delta_row == 0 && delta_col == 0 { 30 | continue; 31 | } 32 | 33 | let neighbor_row = (row + delta_row) % self.height; 34 | let neighbor_col = (column + delta_col) % self.width; 35 | let idx = self.get_index(neighbor_row, neighbor_col); 36 | count += self.cells[idx] as u8; 37 | } 38 | } 39 | 40 | count 41 | } 42 | } 43 | 44 | // 将方法作为公共方法,导出给JavaScript调用 45 | #[wasm_bindgen] 46 | impl Universe { 47 | pub fn tick(&mut self) { 48 | let mut next = self.cells.clone(); 49 | 50 | for row in 0..self.height { 51 | for col in 0..self.width { 52 | let idx = self.get_index(row, col); 53 | let cell = self.cells[idx]; 54 | let live_neighbor = self.live_neighbor_count(row, col); 55 | 56 | next.set(idx, match (cell, live_neighbor) { 57 | // 规则1:模拟生命数量稀少 58 | // 当前细胞为存活状态时,当周围的存活细胞低于2个时(不包含2个),该细胞变成死亡状态。 59 | (true, x) if x < 2 => false, 60 | // 规则2: 61 | // 当前细胞为存活状态时,当周围有2个或3个存活细胞时,该细胞保持原样。 62 | (true, 2) | (true, 3) => true, 63 | // 规则3:模拟生命数量过多 64 | // 当前细胞为存活状态时,当周围有超过3个存活细胞时,该细胞变成死亡状态。 65 | (true, x) if x > 3 => false, 66 | // 规则4:模拟繁殖 67 | // 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 68 | (false, 3) => true, 69 | // 其他细胞保持原状态 70 | (otherwise, _) => otherwise, 71 | }); 72 | } 73 | } 74 | 75 | self.cells = next; 76 | } 77 | 78 | // 初始化存活及死亡细胞 79 | pub fn new() -> Universe { 80 | let width = 120; 81 | let height = 80; 82 | 83 | let size = (width * height) as usize; 84 | let mut cells = FixedBitSet::with_capacity(size); 85 | 86 | for i in 0..size { 87 | // cells.set(i, i % 2 == 0 || i % 7 == 0); 88 | // 每个细胞50%概率随机存活 89 | cells.set(i, js_sys::Math::random() < 0.5); 90 | } 91 | 92 | Universe { 93 | width, 94 | height, 95 | cells, 96 | } 97 | } 98 | 99 | pub fn width(&self) -> u32 { 100 | self.width 101 | } 102 | 103 | pub fn height(&self) -> u32 { 104 | self.height 105 | } 106 | 107 | pub fn cells(&self) -> *const u32 { 108 | self.cells.as_slice().as_ptr() 109 | } 110 | 111 | // 细胞突变: 存活 ⇋ 死亡 112 | pub fn toggle_cell(&mut self, row: u32, column: u32) { 113 | let idx = self.get_index(row, column); 114 | let mut next = self.cells.clone(); 115 | next.toggle(idx); 116 | self.cells = next; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # learn-wasm 2 | 3 | ```bash 4 | # Initialize the project 5 | # https://github.com/lencx/create-mpl 6 | 7 | # npm 6.x 8 | npm init mpl@latest my-app --type web 9 | 10 | # npm 7+, extra double-dash is needed: 11 | npm init mpl@latest my-app -- --type web 12 | ``` 13 | 14 | ## Pre-Install 15 | 16 | ```bash 17 | cargo install rsw wasm-pack 18 | ``` 19 | 20 | ## Start 21 | 22 | ```bash 23 | yarn dev 24 | ``` 25 | 26 | ## [Awesome Lists](./awesome-lists.md) 27 | 28 | * [前端入门 | Rust 和 WebAssembly](https://lencx.github.io/book/wasm/rust_wasm_frontend.html) 29 | * [Awesome WebAssembly](https://mtc.nofwl.com/awesome/wasm.html) 30 | 31 | ## Examples 32 | 33 | ### @rsw/chasm 34 | 35 | \ 36 | 37 | 38 | ### @rsw/game-of-life 39 | 40 | 41 | 42 | ### @rsw/excel-read 43 | 44 | 整理: lencx\ 45 | 作者: 郭宇 \ 46 | 源码地址: [guoyucode/excel_read](https://gitee.com/guoyucode/excel_read) 47 | 48 | ### ffmpeg 49 | -------------------------------------------------------------------------------- /assets/chasm/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/chasm/1.png -------------------------------------------------------------------------------- /assets/chasm/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/chasm/2.png -------------------------------------------------------------------------------- /assets/chasm/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/chasm/3.png -------------------------------------------------------------------------------- /assets/chasm/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/chasm/4.png -------------------------------------------------------------------------------- /assets/game-of-life/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/game-of-life/1.png -------------------------------------------------------------------------------- /assets/game-of-life/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/assets/game-of-life/2.png -------------------------------------------------------------------------------- /awesome-lists.md: -------------------------------------------------------------------------------- 1 | # Awesome Lists 2 | 3 | * [Compiling from Rust to WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm) 4 | * [Rust and WebAssembly](https://rustwasm.github.io) 5 | * [Replacing a hot path in your app's JavaScript with WebAssembly](https://developers.google.com/web/updates/2019/02/hotpath-with-wasm) 6 | * [译: 如何使用 WebAssembly 提升性能](https://www.infoq.cn/article/2IHWa2Ivbvw*hFw6fvk6) 7 | 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Wasm 💖 Rust 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-wasm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "rsw watch & vite", 6 | "rsw": "rsw", 7 | "build": "rsw build && yarn fe:build", 8 | "fe:build": "tsc && vite build" 9 | }, 10 | "dependencies": { 11 | "@ffmpeg/ffmpeg": "^0.11.1", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-router-dom": "^6.3.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.0", 18 | "@types/react-dom": "^18.0.0", 19 | "@vitejs/plugin-react": "^2.0.0", 20 | "sass": "^1.53.0", 21 | "typescript": "^4.8.4", 22 | "vite": "^3.0.4", 23 | "vite-plugin-rsw": "^2.0.11" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | wasm.lencx.tech -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwasm/learn-wasm/ffe4c6bf138c4f03709d0686fdd958107c839f97/public/favicon.ico -------------------------------------------------------------------------------- /rsw.toml: -------------------------------------------------------------------------------- 1 | name = "rsw" 2 | version = "0.1.0" 3 | 4 | #! time interval for file changes to trigger wasm-pack build, default `50` milliseconds 5 | interval = 50 6 | 7 | #! link 8 | #! npm link @see https://docs.npmjs.com/cli/v8/commands/npm-link 9 | #! yarn link @see https://classic.yarnpkg.com/en/docs/cli/link 10 | #! pnpm link @see https://pnpm.io/cli/link 11 | #! The link command will only be executed if `[[crates]] link = true` 12 | #! cli: `npm` | `yarn` | `pnpm`, default is `npm` 13 | cli = "yarn" 14 | 15 | #! --------------------------- 16 | 17 | #! rsw new 18 | [new] 19 | #! @see https://rustwasm.github.io/docs/wasm-pack/commands/new.html 20 | #! using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack` 21 | #! 1. wasm-pack: `rsw new --template