├── .gitignore ├── src ├── lib.typ ├── numfmt.wasm ├── rexllent.wasm ├── numfmt.typ └── mod.typ ├── tests ├── regressions │ ├── test.typ │ ├── .gitignore │ ├── ref │ │ └── 1.png │ └── issue-14 │ │ ├── Test.xlsx │ │ ├── MRE_Bug.typ │ │ └── Bib.bib └── features │ ├── .gitignore │ ├── ref │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 17.png │ ├── 18.png │ ├── 19.png │ ├── 20.png │ ├── 21.png │ └── 22.png │ ├── data │ ├── default.ods │ ├── default.xlsx │ ├── index │ │ └── 1.xlsx │ ├── math │ │ └── 1.xlsx │ ├── cell │ │ ├── fill.xlsx │ │ ├── border.xlsx │ │ ├── merged.xlsx │ │ ├── alignment.xlsx │ │ ├── formatted.xlsx │ │ └── incontinunity.xlsx │ ├── font │ │ ├── bold.xlsx │ │ ├── fill.xlsx │ │ ├── size.xlsx │ │ ├── italic.xlsx │ │ ├── strike.xlsx │ │ └── underline.xlsx │ ├── table │ │ ├── hidden_row.xlsx │ │ ├── row_height.xlsx │ │ ├── zero_height.xlsx │ │ ├── zero_width.xlsx │ │ ├── column_width.xlsx │ │ └── hidden_column.xlsx │ └── not_supported │ │ ├── rotate.xlsx │ │ ├── lowercase.xlsx │ │ └── uppercase.xlsx │ └── test.typ ├── .typstignore ├── assets ├── excel.png ├── example1.png ├── example2.png ├── example3.png ├── example4.png ├── example5.png ├── example6.png ├── example7.png ├── three-line-table.png ├── typst_example1.png ├── typst_example2.png └── typst_example3.png ├── examples ├── monet.xlsx ├── test.xlsx ├── typst.xlsx ├── example.pdf ├── typst_guy.xlsx ├── typst_example.pdf ├── typst_example.typ └── example.typ ├── .gitmodules ├── .github ├── dependabot.yml ├── FUNDING.yml └── workflows │ └── test.yml ├── typst.toml ├── crates └── xlsx-parser-rs │ ├── Cargo.toml │ ├── src │ ├── utils.rs │ ├── data_structures.rs │ ├── worksheet_utils.rs │ ├── cell_utils.rs │ └── lib.rs │ └── Cargo.lock ├── LICENSE ├── justfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | target 3 | .vscode 4 | .DS_Store -------------------------------------------------------------------------------- /src/lib.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": xlsx-parser, spreet-parser -------------------------------------------------------------------------------- /tests/regressions/test.typ: -------------------------------------------------------------------------------- 1 | #include "issue-14/MRE_Bug.typ" 2 | -------------------------------------------------------------------------------- /.typstignore: -------------------------------------------------------------------------------- 1 | crates 2 | external 3 | examples 4 | tests 5 | build.sh -------------------------------------------------------------------------------- /assets/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/excel.png -------------------------------------------------------------------------------- /src/numfmt.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/src/numfmt.wasm -------------------------------------------------------------------------------- /src/rexllent.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/src/rexllent.wasm -------------------------------------------------------------------------------- /tests/features/.gitignore: -------------------------------------------------------------------------------- 1 | # generated by tytanic, do not edit 2 | 3 | diff/** 4 | out/** 5 | -------------------------------------------------------------------------------- /assets/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example1.png -------------------------------------------------------------------------------- /assets/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example2.png -------------------------------------------------------------------------------- /assets/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example3.png -------------------------------------------------------------------------------- /assets/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example4.png -------------------------------------------------------------------------------- /assets/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example5.png -------------------------------------------------------------------------------- /assets/example6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example6.png -------------------------------------------------------------------------------- /assets/example7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/example7.png -------------------------------------------------------------------------------- /examples/monet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/monet.xlsx -------------------------------------------------------------------------------- /examples/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/test.xlsx -------------------------------------------------------------------------------- /examples/typst.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/typst.xlsx -------------------------------------------------------------------------------- /tests/regressions/.gitignore: -------------------------------------------------------------------------------- 1 | # generated by tytanic, do not edit 2 | 3 | diff/** 4 | out/** 5 | -------------------------------------------------------------------------------- /examples/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/example.pdf -------------------------------------------------------------------------------- /examples/typst_guy.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/typst_guy.xlsx -------------------------------------------------------------------------------- /tests/features/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/1.png -------------------------------------------------------------------------------- /tests/features/ref/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/2.png -------------------------------------------------------------------------------- /tests/features/ref/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/3.png -------------------------------------------------------------------------------- /tests/features/ref/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/4.png -------------------------------------------------------------------------------- /tests/features/ref/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/5.png -------------------------------------------------------------------------------- /tests/features/ref/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/6.png -------------------------------------------------------------------------------- /tests/features/ref/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/7.png -------------------------------------------------------------------------------- /tests/features/ref/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/8.png -------------------------------------------------------------------------------- /tests/features/ref/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/9.png -------------------------------------------------------------------------------- /assets/three-line-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/three-line-table.png -------------------------------------------------------------------------------- /assets/typst_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/typst_example1.png -------------------------------------------------------------------------------- /assets/typst_example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/typst_example2.png -------------------------------------------------------------------------------- /assets/typst_example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/assets/typst_example3.png -------------------------------------------------------------------------------- /examples/typst_example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/examples/typst_example.pdf -------------------------------------------------------------------------------- /tests/features/ref/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/10.png -------------------------------------------------------------------------------- /tests/features/ref/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/11.png -------------------------------------------------------------------------------- /tests/features/ref/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/12.png -------------------------------------------------------------------------------- /tests/features/ref/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/13.png -------------------------------------------------------------------------------- /tests/features/ref/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/14.png -------------------------------------------------------------------------------- /tests/features/ref/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/15.png -------------------------------------------------------------------------------- /tests/features/ref/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/16.png -------------------------------------------------------------------------------- /tests/features/ref/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/17.png -------------------------------------------------------------------------------- /tests/features/ref/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/18.png -------------------------------------------------------------------------------- /tests/features/ref/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/19.png -------------------------------------------------------------------------------- /tests/features/ref/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/20.png -------------------------------------------------------------------------------- /tests/features/ref/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/21.png -------------------------------------------------------------------------------- /tests/features/ref/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/ref/22.png -------------------------------------------------------------------------------- /tests/regressions/ref/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/regressions/ref/1.png -------------------------------------------------------------------------------- /tests/features/data/default.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/default.ods -------------------------------------------------------------------------------- /tests/features/data/default.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/default.xlsx -------------------------------------------------------------------------------- /tests/features/data/index/1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/index/1.xlsx -------------------------------------------------------------------------------- /tests/features/data/math/1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/math/1.xlsx -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crates/numfmt-rs"] 2 | path = crates/numfmt-rs 3 | url = https://github.com/hongjr03/numfmt-rs 4 | -------------------------------------------------------------------------------- /tests/features/data/cell/fill.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/fill.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/bold.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/bold.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/fill.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/fill.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/size.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/size.xlsx -------------------------------------------------------------------------------- /tests/features/data/cell/border.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/border.xlsx -------------------------------------------------------------------------------- /tests/features/data/cell/merged.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/merged.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/italic.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/italic.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/strike.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/strike.xlsx -------------------------------------------------------------------------------- /tests/regressions/issue-14/Test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/regressions/issue-14/Test.xlsx -------------------------------------------------------------------------------- /tests/features/data/cell/alignment.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/alignment.xlsx -------------------------------------------------------------------------------- /tests/features/data/cell/formatted.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/formatted.xlsx -------------------------------------------------------------------------------- /tests/features/data/font/underline.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/font/underline.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/hidden_row.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/hidden_row.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/row_height.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/row_height.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/zero_height.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/zero_height.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/zero_width.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/zero_width.xlsx -------------------------------------------------------------------------------- /tests/features/data/cell/incontinunity.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/cell/incontinunity.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/column_width.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/column_width.xlsx -------------------------------------------------------------------------------- /tests/features/data/table/hidden_column.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/table/hidden_column.xlsx -------------------------------------------------------------------------------- /tests/features/data/not_supported/rotate.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/not_supported/rotate.xlsx -------------------------------------------------------------------------------- /tests/features/data/not_supported/lowercase.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/not_supported/lowercase.xlsx -------------------------------------------------------------------------------- /tests/features/data/not_supported/uppercase.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongjr03/typst-rexllent/HEAD/tests/features/data/not_supported/uppercase.xlsx -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /examples/typst_example.typ: -------------------------------------------------------------------------------- 1 | #import "../src/lib.typ": xlsx-parser 2 | #set page(width: auto, height: auto, margin: 1em) 3 | 4 | #xlsx-parser( 5 | read("typst.xlsx", encoding: none), 6 | ) 7 | 8 | #pagebreak() 9 | 10 | #xlsx-parser( 11 | read("typst_guy.xlsx", encoding: none), 12 | ) 13 | 14 | #pagebreak() 15 | 16 | #xlsx-parser( 17 | read("monet.xlsx", encoding: none), 18 | ) 19 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rexllent" 3 | version = "0.4.0" 4 | entrypoint = "src/lib.typ" 5 | authors = ["hongjr03 "] 6 | description = "Parsing xlsx file into a typst table, powered by wasm." 7 | license = "MIT" 8 | repository = "https://github.com/hongjr03/typst-rexllent" 9 | categories = ["visualization", "utility"] 10 | keywords = ["excel", "table"] 11 | exclude = ["assets"] 12 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xlsx-parser-rs" 3 | version = "0.3.4" 4 | authors = ["hongjr03 "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [features] 11 | 12 | [dependencies] 13 | umya-spreadsheet = { version = "2.3.3", features = ["js"] } 14 | toml = "0.9.8" 15 | serde = { version = "1.0.228", features = ["derive"] } 16 | typst-wasm-protocol = "0.0.2" 17 | once_cell = "1.21.3" 18 | getrandom = { version = "0.3", features = ["wasm_js"] } 19 | 20 | [profile.release] 21 | opt-level = "s" 22 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn column_to_number(column: &str) -> u32 { 2 | column 3 | .chars() 4 | .fold(0, |acc, c| acc * 26 + (c as u32 - 'A' as u32 + 1)) 5 | } 6 | 7 | pub fn parse_cell_reference(cell_ref: &str) -> (u32, u32) { 8 | let col_str: String = cell_ref.chars().take_while(|c| c.is_alphabetic()).collect(); 9 | let row: u32 = cell_ref 10 | .chars() 11 | .skip_while(|c| c.is_alphabetic()) 12 | .collect::() 13 | .parse() 14 | .unwrap_or(0); 15 | (column_to_number(&col_str), row) 16 | } 17 | 18 | pub fn parse_merge_range(range: &str) -> (String, String) { 19 | let parts: Vec<&str> = range.split(':').collect(); 20 | (parts[0].to_string(), parts[1].to_string()) 21 | } 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | # polar: # Replace with a single Polar username 13 | # buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | # thanks_dev: # Replace with a single thanks.dev username 15 | custom: ['https://afdian.com/a/hongjr03'] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2025] [Hong Jiarong] 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 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | # ensures this is not run for PRs too 5 | branches: [ main ] 6 | pull_request: 7 | # optional but a good default 8 | branches: [ main ] 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Rust 18 | uses: dtolnay/rust-toolchain@stable 19 | 20 | - name: Install cargo-binstall 21 | uses: taiki-e/install-action@v2 22 | with: 23 | tool: cargo-binstall 24 | 25 | - name: Install tytanic 26 | run: cargo binstall tytanic@0.2.2 -y 27 | 28 | - name: Setup typst 29 | uses: typst-community/setup-typst@v4 30 | with: 31 | typst-version: ^0.13.1 32 | 33 | - name: Run test suite 34 | run: tt run --root . 35 | 36 | - name: Archive diffs 37 | uses: actions/upload-artifact@v4 38 | if: always() 39 | with: 40 | name: artifacts 41 | path: | 42 | tests/**/diff/*.png 43 | tests/**/out/*.png 44 | tests/**/ref/*.png 45 | retention-days: 5 -------------------------------------------------------------------------------- /tests/regressions/issue-14/MRE_Bug.typ: -------------------------------------------------------------------------------- 1 | #import "/src/lib.typ": xlsx-parser 2 | 3 | 4 | #xlsx-parser( 5 | read("Test.xlsx", encoding: none), 6 | parse-alignment: false, 7 | parse-table-style: true, 8 | sheet-index: 0, 9 | parse-header: true, 10 | parse-stroke: false, 11 | parse-font: false, 12 | parse-fill: true, // parse infill true 13 | eval-as-markup: true, 14 | parse-formatted-cell: false, 15 | prepend-elems: (table.hline()), 16 | stroke: (_, y) => { 17 | if y == 0 { 18 | return (bottom: black) 19 | } 20 | }, 21 | align: (left, left, left, left), 22 | columns: (auto, 1fr), 23 | table.hline(), 24 | ) 25 | 26 | 27 | #xlsx-parser( 28 | read("Test.xlsx", encoding: none), 29 | parse-alignment: false, 30 | parse-table-style: true, 31 | sheet-index: 0, 32 | parse-header: true, 33 | parse-stroke: false, 34 | parse-font: false, 35 | parse-fill: false, // parse infill false 36 | eval-as-markup: true, 37 | parse-formatted-cell: false, 38 | prepend-elems: (table.hline()), 39 | stroke: (_, y) => { 40 | if y == 0 { 41 | return (bottom: black) 42 | } 43 | }, 44 | align: (left, left, left, left), 45 | columns: (auto, 1fr), 46 | table.hline(), 47 | ) 48 | 49 | 50 | #bibliography("Bib.bib", style: "apa") 51 | -------------------------------------------------------------------------------- /examples/example.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | 3 | #import "/src/lib.typ": xlsx-parser 4 | 5 | #xlsx-parser(read("test.xlsx", encoding: none)) 6 | 7 | #pagebreak() 8 | 9 | #xlsx-parser( 10 | read("test.xlsx", encoding: none), 11 | parse-table-style: false, 12 | ) 13 | 14 | 15 | #pagebreak() 16 | 17 | #xlsx-parser( 18 | read("test.xlsx", encoding: none), 19 | parse-alignment: false, 20 | ) 21 | 22 | 23 | #pagebreak() 24 | 25 | #xlsx-parser( 26 | read("test.xlsx", encoding: none), 27 | parse-stroke: false, 28 | ) 29 | 30 | 31 | #pagebreak() 32 | 33 | #xlsx-parser( 34 | read("test.xlsx", encoding: none), 35 | parse-fill: false, 36 | ) 37 | 38 | 39 | #pagebreak() 40 | 41 | #xlsx-parser( 42 | read("test.xlsx", encoding: none), 43 | parse-font: false, 44 | ) 45 | 46 | #pagebreak() 47 | 48 | #xlsx-parser( 49 | read("test.xlsx", encoding: none), 50 | parse-table-style: false, 51 | parse-alignment: false, 52 | parse-stroke: false, 53 | parse-fill: false, 54 | parse-font: false, 55 | // args below will be passed to the table 56 | fill: black, 57 | stroke: 4pt + white, 58 | ) 59 | 60 | #pagebreak() 61 | 62 | #xlsx-parser( 63 | read("test.xlsx", encoding: none), 64 | parse-header: true, 65 | parse-stroke: false, 66 | stroke: (_, y) => { 67 | if y == 0 { 68 | return (top: black, bottom: black) 69 | } 70 | }, 71 | table.hline(), 72 | ) -------------------------------------------------------------------------------- /tests/regressions/issue-14/Bib.bib: -------------------------------------------------------------------------------- 1 | @article{abuhammadMedChemVRVirtualReality2021, 2 | title = {``{{MedChemVR}}'': {{A Virtual Reality Game}} to {{Enhance Medicinal Chemistry Education}}}, 3 | shorttitle = {``{{MedChemVR}}''}, 4 | author = {Abuhammad, Areej and Falah, Jannat and Alfalah, Salasabeel F. M. and {Abu-Tarboush}, Muhannad and Tarawneh, Ruba T. and Drikakis, Dimitris and Charissis, Vassilis}, 5 | year = {2021}, 6 | month = mar, 7 | journal = {Multimodal Technologies and Interaction}, 8 | volume = {5}, 9 | number = {3}, 10 | pages = {10}, 11 | issn = {2414-4088}, 12 | doi = {10.3390/mti5030010}, 13 | urldate = {2025-03-04}, 14 | abstract = {Medicinal chemistry (MC) is an indispensable component of the pharmacy curriculum. The pharmacists' unique knowledge of a medicine's chemistry enhances their understanding of the pharmacological activity, manufacturing, storage, use, supply, and handling of drugs. However, chemistry is a challenging subject for both teaching and learning. These challenges are typically caused by the inability of students to construct a mental image of the three-dimensional (3D) structure of a drug molecule from its two-dimensional presentations. This study explores a prototype virtual reality (VR) gamification option, as an educational tool developed to aid the learning process and to improve the delivery of the MC subject to students. The developed system is evaluated by a cohort of 41 students. The analysis of the results was encouraging and provided invaluable feedback for the future development of the proposed system.}, 15 | copyright = {https://creativecommons.org/licenses/by/4.0/}, 16 | langid = {english}, 17 | file = {D:\Dokumente\Nextcloud\Zotero\Abuhammad et al. - 2021 - “MedChemVR” A Virtual Reality Game to Enhance Medicinal Chemistry Education 8.pdf} 18 | } 19 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/src/data_structures.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct TableData { 5 | pub dimensions: TableDimensions, 6 | pub rows: Vec, 7 | pub merged_cells: Vec, 8 | } 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct TableDimensions { 12 | pub columns: Vec, 13 | pub rows: Vec, 14 | pub max_columns: Option, 15 | pub max_rows: Option, 16 | } 17 | 18 | #[derive(Serialize, Deserialize)] 19 | pub struct RowData { 20 | pub row_number: u32, 21 | pub cells: Vec, 22 | } 23 | 24 | #[derive(Serialize, Deserialize)] 25 | pub struct CellData { 26 | pub value: String, 27 | pub column: u32, 28 | pub style: Option, 29 | pub format: String, 30 | pub data_type: String, 31 | } 32 | 33 | #[derive(Serialize, Deserialize)] 34 | pub struct CellStyle { 35 | pub alignment: Option, 36 | pub border: Option, 37 | pub color: Option, 38 | pub font: Option, 39 | } 40 | 41 | #[derive(Serialize, Deserialize)] 42 | pub struct Position { 43 | pub row: u32, 44 | pub column: u32, 45 | } 46 | 47 | #[derive(Serialize, Deserialize)] 48 | pub struct MergedCell { 49 | pub range: String, 50 | pub start: Position, 51 | pub end: Position, 52 | } 53 | 54 | #[derive(Serialize, Deserialize)] 55 | pub struct Alignment { 56 | pub horizontal: String, 57 | pub vertical: String, 58 | } 59 | 60 | #[derive(Serialize, Deserialize)] 61 | pub struct Border { 62 | pub left: bool, 63 | pub right: bool, 64 | pub top: bool, 65 | pub bottom: bool, 66 | } 67 | 68 | #[derive(Serialize, Deserialize)] 69 | pub struct FontStyle { 70 | pub bold: bool, 71 | pub italic: bool, 72 | pub size: f64, 73 | pub color: Option, 74 | pub underline: bool, 75 | pub strike: bool, 76 | } 77 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/src/worksheet_utils.rs: -------------------------------------------------------------------------------- 1 | use umya_spreadsheet::Worksheet; 2 | 3 | use crate::utils::parse_cell_reference; 4 | 5 | pub fn get_table_dimensions(worksheet: &Worksheet) -> Result<(u32, u32), String> { 6 | let mut max_col = 0; 7 | let mut max_row = 0; 8 | 9 | for cell in worksheet.get_cell_collection() { 10 | let (col_num, row_num) = parse_cell_reference(&cell.get_coordinate().to_string()); 11 | max_col = max_col.max(col_num); 12 | max_row = max_row.max(row_num); 13 | } 14 | 15 | if max_col == 0 || max_row == 0 { 16 | return Err("No data found in the worksheet".to_string()); 17 | } 18 | 19 | Ok((max_col, max_row)) 20 | } 21 | 22 | pub fn get_column_widths( 23 | worksheet: &Worksheet, 24 | max_col: u32, 25 | default_width: f64, 26 | column_mapping: Option<&Vec>, 27 | ) -> Vec { 28 | if let Some(mapping) = column_mapping { 29 | // Return widths only for selected columns 30 | let all_widths = get_all_column_widths(worksheet, max_col, default_width); 31 | mapping 32 | .iter() 33 | .map(|&col_num| { 34 | let idx = (col_num - 1) as usize; 35 | if idx < all_widths.len() { 36 | all_widths[idx] 37 | } else { 38 | default_width 39 | } 40 | }) 41 | .collect() 42 | } else { 43 | get_all_column_widths(worksheet, max_col, default_width) 44 | } 45 | } 46 | 47 | fn get_all_column_widths(worksheet: &Worksheet, max_col: u32, default_width: f64) -> Vec { 48 | let mut columns = vec![default_width; max_col as usize]; 49 | for col in worksheet.get_column_dimensions() { 50 | let col_idx = *col.get_col_num() as usize - 1; 51 | if col_idx < columns.len() { 52 | columns[col_idx] = *col.get_width(); 53 | } 54 | } 55 | columns 56 | } 57 | 58 | pub fn get_row_heights(worksheet: &Worksheet, max_row: u32, default_height: f64) -> Vec { 59 | let mut rows = vec![default_height; max_row as usize]; 60 | for row in worksheet.get_row_dimensions() { 61 | let row_idx = (*row.get_row_num() as usize) - 1; 62 | if row_idx < rows.len() { 63 | rows[row_idx] = *row.get_height(); 64 | } 65 | } 66 | rows 67 | } 68 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Configuration Variables 2 | wasm_target := "wasm32-unknown-unknown" 3 | wasi_target := "wasm32-wasip1" 4 | xlsx_parser_crate_dir := "crates/xlsx-parser-rs" 5 | numfmt_crate_dir := "crates/numfmt-rs" 6 | src_dir := "./src" 7 | xlsx_parser_wasm_output_name := "rexllent.wasm" 8 | numfmt_wasm_output_name := "numfmt.wasm" 9 | xlsx_parser_intermediate_wasm := "xlsx_parser_rs.wasm" 10 | numfmt_intermediate_wasm := "numfmt_rs.wasm" 11 | wasm_opt_flags := "-O3 --enable-bulk-memory" 12 | 13 | # Build and optimize xlsx-parser-rs 14 | build-xlsx-parser: 15 | #!/usr/bin/env bash 16 | set -e 17 | echo "Building xlsx-parser-rs..." 18 | if [ -f "{{src_dir}}/{{xlsx_parser_wasm_output_name}}" ]; then 19 | echo "Removing existing {{src_dir}}/{{xlsx_parser_wasm_output_name}}..." 20 | rm "{{src_dir}}/{{xlsx_parser_wasm_output_name}}" 21 | fi 22 | echo "Adding Rust target: {{wasi_target}}" 23 | rustup target add "{{wasi_target}}" 24 | echo "Building {{xlsx_parser_crate_dir}} for target {{wasi_target}}..." 25 | pushd "{{xlsx_parser_crate_dir}}" 26 | cargo clean 27 | cargo build --release --target "{{wasi_target}}" 28 | popd 29 | echo "Running wasi-stub on {{xlsx_parser_crate_dir}}/target/{{wasi_target}}/release/{{xlsx_parser_intermediate_wasm}}..." 30 | wasi-stub -r 0 "{{xlsx_parser_crate_dir}}/target/{{wasi_target}}/release/{{xlsx_parser_intermediate_wasm}}" -o "{{src_dir}}/{{xlsx_parser_wasm_output_name}}" 31 | echo "Optimizing {{src_dir}}/{{xlsx_parser_wasm_output_name}}..." 32 | wasm-opt "{{src_dir}}/{{xlsx_parser_wasm_output_name}}" {{wasm_opt_flags}} -o "{{src_dir}}/{{xlsx_parser_wasm_output_name}}" 33 | echo "Successfully built {{src_dir}}/{{xlsx_parser_wasm_output_name}}" 34 | 35 | # Build and optimize numfmt-rs using its justfile 36 | build-numfmt: 37 | #!/usr/bin/env bash 38 | set -e 39 | echo "Building numfmt-rs..." 40 | if [ -f "{{src_dir}}/{{numfmt_wasm_output_name}}" ]; then 41 | echo "Removing existing {{src_dir}}/{{numfmt_wasm_output_name}}..." 42 | rm "{{src_dir}}/{{numfmt_wasm_output_name}}" 43 | fi 44 | echo "Building numfmt-rs using its justfile..." 45 | just -f "{{numfmt_crate_dir}}/justfile" build-wasm-typst 46 | echo "Optimizing to {{src_dir}}/{{numfmt_wasm_output_name}}..." 47 | wasm-opt "{{numfmt_crate_dir}}/target/{{wasm_target}}/release/{{numfmt_intermediate_wasm}}" {{wasm_opt_flags}} -o "{{src_dir}}/{{numfmt_wasm_output_name}}" 48 | echo "Successfully built {{src_dir}}/{{numfmt_wasm_output_name}}" 49 | 50 | # Build all 51 | build-all: build-xlsx-parser build-numfmt 52 | @echo "All builds complete." 53 | -------------------------------------------------------------------------------- /tests/features/test.typ: -------------------------------------------------------------------------------- 1 | #set page(width: auto, height: auto, margin: 1em) 2 | #show heading: it => pagebreak(weak: true) 3 | 4 | #import "/src/lib.typ": xlsx-parser, spreet-parser 5 | 6 | = Tests 7 | 8 | == default 9 | 10 | #xlsx-parser(read("./data/default.xlsx", encoding: none)) 11 | 12 | == cell 13 | 14 | === alignment 15 | 16 | #(xlsx-parser(read("./data/cell/alignment.xlsx", encoding: none), parse-stroke: false, stroke: black + 1pt)) 17 | 18 | === border 19 | 20 | #xlsx-parser(read("./data/cell/border.xlsx", encoding: none)) 21 | 22 | === fill 23 | 24 | #xlsx-parser(read("./data/cell/fill.xlsx", encoding: none)) 25 | 26 | === incontinunity 27 | 28 | #xlsx-parser(read("./data/cell/incontinunity.xlsx", encoding: none)) 29 | 30 | === merged 31 | 32 | #xlsx-parser(read("./data/cell/merged.xlsx", encoding: none)) 33 | 34 | === formatted 35 | 36 | #xlsx-parser(read("./data/cell/formatted.xlsx", encoding: none), parse-formatted-cell: true) 37 | 38 | == font 39 | 40 | === bold 41 | 42 | #xlsx-parser(read("./data/font/bold.xlsx", encoding: none)) 43 | 44 | === fill 45 | 46 | #xlsx-parser(read("./data/font/fill.xlsx", encoding: none)) 47 | 48 | === italic 49 | 50 | #xlsx-parser(read("./data/font/italic.xlsx", encoding: none)) 51 | 52 | === size 53 | 54 | #xlsx-parser(read("./data/font/size.xlsx", encoding: none)) 55 | 56 | === strike 57 | 58 | #xlsx-parser(read("./data/font/strike.xlsx", encoding: none)) 59 | 60 | == index 61 | 62 | === 1 63 | 64 | #xlsx-parser(read("./data/index/1.xlsx", encoding: none), sheet-index: 1) 65 | 66 | === sheet name 67 | 68 | #xlsx-parser(read("./data/index/1.xlsx", encoding: none), sheet-name: "Sheet2") 69 | 70 | == columns 71 | 72 | === select columns 73 | 74 | #(xlsx-parser(read("./data/index/1.xlsx", encoding: none), selected-cols: ("A", "B"))) 75 | 76 | == table 77 | 78 | === column_width 79 | 80 | #xlsx-parser(read("./data/table/column_width.xlsx", encoding: none)) 81 | 82 | === row_height 83 | 84 | #xlsx-parser(read("./data/table/row_height.xlsx", encoding: none)) 85 | 86 | === zero_height 87 | 88 | #xlsx-parser(read("./data/table/zero_height.xlsx", encoding: none)) 89 | 90 | === zero_width 91 | 92 | #xlsx-parser(read("./data/table/zero_width.xlsx", encoding: none)) 93 | 94 | === hidden_column 95 | 96 | #xlsx-parser(read("./data/table/hidden_column.xlsx", encoding: none)) 97 | 98 | === hidden_row 99 | 100 | #xlsx-parser(read("./data/table/hidden_row.xlsx", encoding: none)) 101 | 102 | == math 103 | 104 | #xlsx-parser(read("./data/math/1.xlsx", encoding: none), eval-as-markup: true) 105 | 106 | // == not_supported 107 | 108 | // === lowercase 109 | 110 | // #xlsx-parser(read("./data/not_supported/lowercase.xlsx", encoding: none)) 111 | 112 | // === rotate 113 | 114 | // #xlsx-parser(read("./data/not_supported/rotate.xlsx", encoding: none)) 115 | 116 | // === uppercase 117 | 118 | // #xlsx-parser(read("./data/not_supported/uppercase.xlsx", encoding: none)) 119 | 120 | // == spreet 121 | 122 | // #import "@preview/spreet:0.1.0" 123 | 124 | // #spreet-parser(spreet.decode(read("./data/default.ods", encoding: none))) 125 | 126 | -------------------------------------------------------------------------------- /src/numfmt.typ: -------------------------------------------------------------------------------- 1 | #let numfmt = plugin("numfmt.wasm") 2 | // #dictionary(numfmt) 3 | 4 | #let format(format, value, opt: (:)) = { 5 | str(numfmt.format(bytes(format), bytes(str(value)), bytes(json.encode(opt)))) 6 | } 7 | 8 | #let format-color(format, value) = { 9 | let color = json(numfmt.format-color(bytes(format), bytes(str(value)))) 10 | 11 | // https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/ 12 | let color-map = ( 13 | "black": rgb(0, 0, 0), 14 | "white": rgb(255, 255, 255), 15 | "red": rgb(255, 0, 0), 16 | "green": rgb(0, 255, 0), 17 | "blue": rgb(0, 0, 255), 18 | "yellow": rgb(255, 255, 0), 19 | "magenta": rgb(255, 0, 255), 20 | "cyan": rgb(0, 255, 255), 21 | "color9": rgb(128, 0, 0), 22 | "color10": rgb(0, 128, 0), 23 | "color11": rgb(0, 0, 128), 24 | "color12": rgb(128, 128, 0), 25 | "color13": rgb(128, 0, 128), 26 | "color14": rgb(0, 128, 128), 27 | "color15": rgb(192, 192, 192), 28 | "color16": rgb(128, 128, 128), 29 | "color17": rgb(153, 153, 255), 30 | "color18": rgb(153, 51, 102), 31 | "color19": rgb(255, 255, 204), 32 | "color20": rgb(204, 255, 255), 33 | "color21": rgb(102, 0, 102), 34 | "color22": rgb(255, 128, 128), 35 | "color23": rgb(0, 102, 204), 36 | "color24": rgb(204, 204, 255), 37 | "color25": rgb(0, 0, 128), 38 | "color26": rgb(255, 0, 255), 39 | "color27": rgb(255, 255, 0), 40 | "color28": rgb(0, 255, 255), 41 | "color29": rgb(128, 0, 128), 42 | "color30": rgb(128, 0, 0), 43 | "color31": rgb(0, 128, 128), 44 | "color32": rgb(0, 0, 255), 45 | "color33": rgb(0, 204, 255), 46 | "color34": rgb(204, 255, 255), 47 | "color35": rgb(204, 255, 204), 48 | "color36": rgb(255, 255, 153), 49 | "color37": rgb(153, 204, 255), 50 | "color38": rgb(255, 153, 204), 51 | "color39": rgb(204, 153, 255), 52 | "color40": rgb(255, 204, 153), 53 | "color41": rgb(51, 102, 255), 54 | "color42": rgb(51, 204, 204), 55 | "color43": rgb(153, 204, 0), 56 | "color44": rgb(255, 204, 0), 57 | "color45": rgb(255, 153, 0), 58 | "color46": rgb(255, 102, 0), 59 | "color47": rgb(102, 102, 153), 60 | "color48": rgb(150, 150, 150), 61 | "color49": rgb(0, 51, 102), 62 | "color50": rgb(51, 153, 102), 63 | "color51": rgb(0, 51, 0), 64 | "color52": rgb(51, 51, 0), 65 | "color53": rgb(153, 51, 0), 66 | "color54": rgb(153, 51, 102), 67 | "color55": rgb(51, 51, 153), 68 | "color56": rgb(51, 51, 51), 69 | ) 70 | 71 | if (color == none) { 72 | return color 73 | } 74 | if (color.type == "string") { 75 | let result = color-map.at(lower(color.value)) 76 | if (result == none) { 77 | rgb(color.value) 78 | } else { 79 | result 80 | } 81 | } 82 | } 83 | 84 | // #numfmt.get-locale() 85 | 86 | // // #locale("zh-CN") 87 | 88 | // // #call-js-function(numfmt-bytecode, "getLocale", "zh-CN") 89 | 90 | // // #format("d dd ddd dddd ddddd", 3290.1278435, (locale: "zh-CH", overflow: "######")) 91 | // #format("[$-F800]dddd\,\ mmmm\ dd\,\ yyy", 3290.1278435, opt: (locale: "zh-CN")) 92 | // #format("[>=100]\"A\"0;[<=-100]\"B\"0;\"C\"0", 6.3) 93 | 94 | // #format-color("[color 0]0", 0) 95 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/src/cell_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::data_structures::{Alignment, Border, FontStyle}; 2 | use umya_spreadsheet::{ 3 | BorderStyleValues, Cell, HorizontalAlignmentValues, NumberingFormat, Spreadsheet, 4 | UnderlineValues, VerticalAlignmentValues, 5 | }; 6 | 7 | pub fn cell_type(cell: &Cell) -> Result { 8 | if cell.get_raw_value().is_error() { 9 | return Err(format!( 10 | "Error in cell {}", 11 | cell.get_coordinate().to_string() 12 | )); 13 | } else { 14 | Ok(cell.get_data_type().to_string()) 15 | } 16 | } 17 | 18 | pub fn cell_format_code(cell: &Cell) -> Result { 19 | if cell.get_raw_value().is_error() { 20 | return Err(format!( 21 | "Error in cell {}", 22 | cell.get_coordinate().to_string() 23 | )); 24 | } else { 25 | Ok(cell 26 | .get_style() 27 | .get_number_format() 28 | .unwrap_or(&NumberingFormat::default()) 29 | .get_format_code() 30 | .to_string()) 31 | } 32 | } 33 | 34 | pub fn cell_value(cell: &Cell) -> Result { 35 | if cell.get_raw_value().is_error() { 36 | return Err(format!( 37 | "Error in cell {}", 38 | cell.get_coordinate().to_string() 39 | )); 40 | } else { 41 | Ok(cell.get_value().to_string()) 42 | } 43 | } 44 | 45 | pub fn get_cell_alignment(cell: &Cell) -> Option { 46 | let style = cell.get_style(); 47 | let alignment = match style.get_alignment() { 48 | Some(alignment) => alignment, 49 | None => return None, 50 | }; 51 | 52 | Some(Alignment { 53 | horizontal: match alignment.get_horizontal() { 54 | HorizontalAlignmentValues::Left => "left", 55 | HorizontalAlignmentValues::Center => "center", 56 | HorizontalAlignmentValues::Right => "right", 57 | _ => "default", 58 | } 59 | .to_string(), 60 | vertical: match alignment.get_vertical() { 61 | VerticalAlignmentValues::Bottom => "bottom", 62 | VerticalAlignmentValues::Center => "center", 63 | VerticalAlignmentValues::Top => "top", 64 | _ => "default", 65 | } 66 | .to_string(), 67 | }) 68 | } 69 | 70 | pub fn get_cell_border(cell: &Cell) -> Option { 71 | let style = cell.get_style(); 72 | let border = match style.get_borders() { 73 | Some(border) => border, 74 | None => return None, 75 | }; 76 | 77 | Some(Border { 78 | left: border.get_left().get_style() != &BorderStyleValues::None, 79 | right: border.get_right().get_style() != &BorderStyleValues::None, 80 | top: border.get_top().get_style() != &BorderStyleValues::None, 81 | bottom: border.get_bottom().get_style() != &BorderStyleValues::None, 82 | }) 83 | } 84 | 85 | pub fn get_cell_bg_color(cell: &Cell, book: &Spreadsheet) -> Option { 86 | let style = cell.get_style(); 87 | let color = style.get_background_color()?; 88 | let argb = color.get_argb_with_theme(book.get_theme()); 89 | if argb.is_empty() { 90 | Some("".to_string()) 91 | } else { 92 | Some(if argb.len() == 8 { 93 | argb.chars().skip(2).collect::() // skip 的作用是去掉前两位,即 alpha 通道 94 | } else { 95 | argb.to_string() 96 | }) 97 | } 98 | } 99 | 100 | pub fn get_cell_font_style(cell: &Cell, book: &Spreadsheet) -> Option { 101 | let font = match cell.get_style().get_font() { 102 | Some(font) => font, 103 | None => { 104 | return None; 105 | } 106 | }; 107 | 108 | Some(FontStyle { 109 | bold: *font.get_font_bold().get_val(), 110 | italic: *font.get_font_italic().get_val(), 111 | size: *font.get_font_size().get_val(), 112 | color: { 113 | let argb = font.get_color().get_argb_with_theme(book.get_theme()); 114 | if argb.is_empty() { 115 | None 116 | } else { 117 | Some(if argb.len() == 8 { 118 | argb.chars().skip(2).collect::() // skip 的作用是去掉前两位,即 alpha 通道 119 | } else { 120 | argb.to_string() 121 | }) 122 | } 123 | }, 124 | underline: font.get_font_underline().get_val() != &UnderlineValues::None, 125 | strike: *font.get_font_strike().get_val(), 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🦖 ReXLlenT 2 | 3 | [![Universe](https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Ftypst.app%2Funiverse%2Fpackage%2Frexllent&query=%2Fhtml%2Fbody%2Fdiv%2Fmain%2Fdiv%5B2%5D%2Faside%2Fsection%5B2%5D%2Fdl%2Fdd%5B3%5D&logo=typst&label=Universe&color=%2339cccc)](https://typst.app/universe/package/rexllent) 4 | [![GitHub](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fhongjr03%2Ftypst-rexllent%2Frefs%2Fheads%2Fmain%2Ftypst.toml&query=package.version&logo=GitHub&label=GitHub)](https://github.com/hongjr03/typst-rexllent) 5 | [![Test](https://github.com/hongjr03/typst-rexllent/actions/workflows/test.yml/badge.svg)](https://github.com/hongjr03/typst-rexllent/actions/workflows/test.yml) 6 | 7 | ReXLlenT is a typst package that helps you: 8 | 9 | - Convert Excel **xlsx** tables to typst tables, powered by wasm. 10 | - Convert [Spreet](https://github.com/lublak/typst-spreet-package) parsed tables to typst tables. (Supports excel/opendocument spreadsheets but doesn't support parsing styles or merge cells.) 11 | 12 | ## Usage 13 | 14 | Start by importing the package: 15 | 16 | ```typ 17 | #import "@preview/rexllent:0.4.0": xlsx-parser 18 | ``` 19 | 20 | Then you can use `xlsx-parser` function to convert your xlsx Excel table to typst table. Here is an example: 21 | 22 | ```typ 23 | #xlsx-parser(read("test.xlsx", encoding: none)) 24 | ``` 25 | 26 | By passing `sheet-index` parameter, you can specify the sheet index to parse. The default value is 0. 27 | 28 | ```typ 29 | #xlsx-parser(read("test.xlsx", encoding: none), sheet-index: 1) 30 | ``` 31 | 32 | By toggling parameters below, you can customize the output table: 33 | 34 | - `parse-table-style`: Parse table style(columns width, rows height), default is `true`. 35 | - `parse-alignment`: Parse cell content alignment, default is `true`. 36 | - `parse-stroke`: Parse cell stroke, default is `true`. 37 | - `parse-fill`: Parse cell fill, default is `true`. 38 | - `parse-font`: Parse font style, default is `true`. 39 | - `parse-header`: Parse header row, default is `false`. 40 | - `parse-formatted-cell`: Parse formatted cell, default is `false`. Notice that this will cause great performance loss so don't use it unless you need it. 41 | - `eval-as-markup`: Evaluate cell content as typst markup, default is `false`. 42 | 43 | _Notice that 0pt height or 0pt width will be parsed as `auto`. Disable `parse-table-style` to prevent this behavior and set the width and height manually._ 44 | 45 | Extra arguments passed to `xlsx-parser` function will be passed to `table`. Feel free to customize the output table. For the prepend elements(for example: header, hline) you should pass them as array to `prepend-elems` parameter. 46 | 47 | ```typ 48 | #xlsx-parser( 49 | read("test.xlsx", encoding: none), 50 | parse-header: true, 51 | parse-stroke: false, 52 | prepend-elems: (table.hline()), 53 | stroke: (_, y) => { 54 | if y == 0 { 55 | return (bottom: black) 56 | } 57 | }, 58 | table.hline(), 59 | ) 60 | ``` 61 | 62 | ![three-line-table](assets/three-line-table.png) 63 | 64 | ## Work with Spreet 65 | 66 | You can also convert Spreet parsed tables to typst tables. Here is an example: 67 | 68 | ```typ 69 | #import "@preview/spreet:0.1.0" 70 | #import "@preview/rexllent:0.4.0": spreet-parser 71 | 72 | #spreet-parser(spreet.decode(read("/tests/data/default.ods", encoding: none))) 73 | ``` 74 | 75 | By passing `sheet-index` parameter, you can specify the sheet index to parse. The default value is 0. The extra arguments passed to `spreet-parser` function will be passed to `table`. 76 | 77 | Through this way, you can convert excel/opendocument spreadsheets to typst tables. However, the styles and merge cells will not be parsed due to the limitation of calamine. 78 | 79 | ## Example 80 | 81 | - Excel Table 82 | 83 | ![Excel](assets/excel.png) 84 | 85 | - Typst Table (with default parameters) 86 | 87 | ![Typst](assets/example1.png) 88 | 89 | - Typst Table (with `parse-table-style: false`) 90 | 91 | ![Typst](assets/example2.png) 92 | 93 | - Typst Table (with `parse-alignment: false`) 94 | 95 | ![Typst](assets/example3.png) 96 | 97 | - Typst Table (with `parse-stroke: false`) 98 | 99 | ![Typst](assets/example4.png) 100 | 101 | - Typst Table (with `parse-fill: false`) 102 | 103 | ![Typst](assets/example5.png) 104 | 105 | - Typst Table (with `parse-font: false`) 106 | 107 | ![Typst](assets/example6.png) 108 | 109 | - With Custom Style 110 | 111 | ![Typst](assets/example7.png) 112 | 113 | ## Pixel Art 114 | 115 | You can also convert pixel art to typst table using ReXLlenT. Here are some examples: 116 | 117 | - Typst Logo 118 | 119 | ![typst](assets/typst_example1.png) 120 | 121 | - Typst Guy 122 | 123 | ![typst_guy](assets/typst_example2.png) 124 | 125 | - _Impression, soleil levant_ 126 | 127 | ![monet](assets/typst_example3.png) 128 | 129 | ## TODOs 130 | 131 | ReXLlenT is still in development and PRs are welcome. Here are some TODOs (also limitations): 132 | 133 | - [ ] Implement in-cell image parsing. 134 | - [ ] Prevent parsing errors caused by special characters. 135 | - [ ] Parse auto width and height instead of treating 0pt as auto. 136 | - [ ] Handle hidden rows and columns. 137 | - ... 138 | 139 | ## Credits 140 | 141 | - [lublak/typst-spreet-package](https://github.com/lublak/typst-spreet-package) 142 | - [MathNya/umya-spreadsheet](https://github.com/MathNya/umya-spreadsheet) 143 | - [borgar/numfmt](https://github.com/borgar/numfmt) 144 | 145 | ## License 146 | 147 | This package is licensed under the MIT License. 148 | 149 | ## Star History 150 | 151 | 152 | 153 | 154 | 155 | Star History Chart 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/mod.typ: -------------------------------------------------------------------------------- 1 | #import "numfmt.typ": format, format-color 2 | 3 | #let p = plugin("rexllent.wasm") 4 | 5 | // 处理RGB颜色转换 6 | #let to-rgb(color) = { 7 | if color == none { return none } 8 | rgb(color) 9 | } 10 | 11 | // 处理尺寸转换 12 | #let to-size(value, unit) = { 13 | if value == none or value == 0.0 { return auto } 14 | eval(str(value) + unit) 15 | } 16 | 17 | // 创建文本样式 18 | #let apply-text-style(content, font) = { 19 | if font == none { return content } 20 | 21 | let text_args = (:) 22 | if font.bold { text_args.insert("weight", "bold") } 23 | if font.italic { text_args.insert("style", "italic") } 24 | if font.size != none { text_args.insert("size", to-size(font.size, "pt")) } 25 | if font.color != none { text_args.insert("fill", to-rgb(font.color)) } 26 | 27 | let styled = text(..text_args)[#content] 28 | 29 | if font.underline { styled = underline[#styled] } 30 | if font.strike { styled = strike[#styled] } 31 | 32 | return styled 33 | } 34 | 35 | // 处理单元格对齐方式 36 | #let process-alignment(alignment) = { 37 | if alignment == none { return none } 38 | 39 | let align = () 40 | if alignment.horizontal != "default" { 41 | align.push(alignment.horizontal) 42 | } 43 | if alignment.vertical != "default" { 44 | // 垂直居中在typst中用"horizon"表示 45 | let v_align = if alignment.vertical == "center" { "horizon" } else { alignment.vertical } 46 | align.push(v_align) 47 | } 48 | 49 | if align.len() > 0 { 50 | return eval(align.join("+")) 51 | } 52 | return none 53 | } 54 | 55 | // 辅助函数:创建单元格内容和样式 56 | #let create_cell_content(cell, formatted-cell, eval-as-markup: false) = { 57 | let content = if ( 58 | formatted-cell and cell.format != "General" and cell.data_type == "n" 59 | ) { 60 | let formatted = format( 61 | cell.format, 62 | cell.value, 63 | ) 64 | 65 | let color = format-color(cell.format, cell.value) 66 | text(..if color != none { (fill: color) }, formatted) 67 | } else if eval-as-markup { 68 | eval(cell.value, mode: "markup") 69 | } else { 70 | cell.value 71 | } 72 | 73 | // should produce content first, otherwise the eval-as-markup won't work 74 | if not cell.keys().contains("style") or cell.style == none { 75 | return ({}, content) 76 | } 77 | 78 | let style = cell.style 79 | let cell_args = (:) 80 | 81 | // 处理字体样式 82 | if style.keys().contains("font") and style.font != none { 83 | content = apply-text-style(content, style.font) 84 | } 85 | 86 | // 处理对齐 87 | if style.keys().contains("alignment") { 88 | let align = process-alignment(style.alignment) 89 | if align != none { 90 | cell_args.insert("align", align) 91 | } 92 | } 93 | 94 | // 处理边框 95 | if style.keys().contains("border") and style.border != none { 96 | let stroke_args = (:) 97 | for (border, value) in style.border { 98 | if value == false { 99 | stroke_args.insert(border, none) 100 | } 101 | } 102 | if stroke_args.len() > 0 { 103 | cell_args.insert("stroke", stroke_args) 104 | } 105 | } 106 | 107 | // 处理填充 108 | if style.keys().contains("color") { 109 | let fill = to-rgb(style.color) 110 | if fill != none { 111 | cell_args.insert("fill", fill) 112 | } 113 | } 114 | 115 | return (cell_args, content) 116 | } 117 | 118 | #let parse_excel_table( 119 | data, 120 | prepend-elems: (), 121 | parse-header: false, 122 | parse-table-style: true, 123 | parse-stroke: true, 124 | parse-formatted-cell: false, 125 | eval-as-markup: false, 126 | ..args, 127 | ) = { 128 | // 解析维度信息 129 | let dims = data.dimensions 130 | 131 | // 创建表格参数 132 | let table_args = (:) 133 | 134 | // 设置列宽和行高 135 | if dims.columns != none and dims.rows != none { 136 | let columns = dims.columns.map(c => if c != 0.0 { eval(str(c * 0.1) + "in") } else { auto }) 137 | let rows = dims.rows.map(r => if r != 0.0 { eval(str(r) + "pt") } else { auto }) 138 | if parse-table-style { 139 | table_args.insert("columns", columns) 140 | } else { 141 | table_args.insert("columns", dims.max_columns) 142 | } 143 | if parse-table-style { 144 | table_args.insert("rows", rows) 145 | } else { 146 | table_args.insert("rows", dims.max_rows) 147 | } 148 | } 149 | // 创建合并单元格映射 150 | let merged = (:) 151 | for mc in data.merged_cells { 152 | // 记录所有被合并的单元格位置 153 | for r in range(mc.start.row, mc.end.row + 1) { 154 | for c in range(mc.start.column, mc.end.column + 1) { 155 | let key = str(r) + "," + str(c) 156 | merged.insert( 157 | key, 158 | ( 159 | is_start: r == mc.start.row and c == mc.start.column, 160 | rowspan: mc.end.row - mc.start.row + 1, 161 | colspan: mc.end.column - mc.start.column + 1, 162 | ), 163 | ) 164 | } 165 | } 166 | } 167 | 168 | // 处理每一行 169 | let cells = () 170 | let header_cells = () 171 | for row in data.rows { 172 | // 创建单元格映射,方便快速查找 173 | let cell_map = (:) 174 | for cell in row.cells { 175 | cell_map.insert(str(cell.column), cell) 176 | } 177 | 178 | // 处理这一行的每一列 179 | for col in range(1, dims.max_columns + 1) { 180 | let pos_key = str(row.row_number) + "," + str(col) 181 | 182 | // 检查是否是被合并的单元格 183 | if merged.at(pos_key, default: none) != none { 184 | let merge_info = merged.at(pos_key) 185 | if merge_info.is_start { 186 | // 是合并单元格的起始点,创建带合并属性的单元格 187 | let cell = cell_map.at(str(col), default: none) 188 | if cell == none { continue } 189 | 190 | let cell_args = ( 191 | rowspan: merge_info.rowspan, 192 | colspan: merge_info.colspan, 193 | ) 194 | 195 | // 处理样式和内容 196 | let (_cell_args, content) = create_cell_content(cell, parse-formatted-cell, eval-as-markup: eval-as-markup) 197 | cell_args += _cell_args 198 | if row.row_number == 1 and parse-header { 199 | header_cells.push(table.cell(..cell_args)[#content]) 200 | } else { 201 | cells.push(table.cell(..cell_args)[#content]) 202 | } 203 | } 204 | // 如果不是起始点,跳过这个单元格 205 | continue 206 | } 207 | 208 | // 处理普通单元格 209 | let cell = cell_map.at(str(col), default: none) 210 | if cell != none { 211 | let (_cell_args, content) = create_cell_content(cell, parse-formatted-cell, eval-as-markup: eval-as-markup) 212 | if row.row_number == 1 and parse-header { 213 | header_cells.push(table.cell(.._cell_args)[#content]) 214 | } else { 215 | cells.push(table.cell(.._cell_args)[#content]) 216 | } 217 | } else { 218 | // 空单元格 219 | if parse-stroke { 220 | if row.row_number == 1 and parse-header { 221 | header_cells.push(table.cell(stroke: none)[#none]) 222 | } else { cells.push(table.cell(stroke: none)[#none]) } 223 | } else { 224 | if row.row_number == 1 and parse-header { 225 | header_cells.push([]) 226 | } else { cells.push([]) } 227 | } 228 | } 229 | } 230 | } 231 | if type(prepend-elems) != array { 232 | prepend-elems = (prepend-elems,) 233 | } 234 | if parse-header { 235 | table(..table_args, ..prepend-elems, table.header(..header_cells), ..cells, ..args) 236 | } else { 237 | table(..table_args, ..prepend-elems, ..cells, ..args) 238 | } 239 | } 240 | 241 | /// Parse the xlsx file content and return the table. 242 | /// 243 | /// - xlsx (bytes): Pass the xlsx file content by `read("path/to/file.xlsx", encoding: none)`. 244 | /// - prepend-elems (array): Arguments to be prepended to the table. 245 | /// - sheet-index (integer): The index of the sheet to be parsed. 246 | /// - sheet-name (string): The name of the sheet to be parsed (alternative to sheet-index). 247 | /// - selected-cols (string): Comma-separated list of columns to include (e.g., "A,C,F"). 248 | /// - parse-table-style (boolean): Whether to parse the table style(like column width and row height). 249 | /// - parse-alignment (boolean): Whether to parse the cell alignment. 250 | /// - parse-stroke (boolean): Whether to parse the cell border. 251 | /// - parse-fill (boolean): Whether to parse the cell fill color. 252 | /// - parse-font (boolean): Whether to parse the cell font style. 253 | /// - parse-header (boolean): Whether to parse the header row. 254 | /// - apprend-args (arguments): Other arguments for the table. 255 | /// -> table 256 | #let xlsx-parser( 257 | xlsx, 258 | prepend-elems: (), 259 | sheet-index: 0, 260 | sheet-name: none, 261 | selected-cols: none, 262 | parse-table-style: true, 263 | parse-alignment: true, 264 | parse-stroke: true, 265 | parse-fill: true, 266 | parse-font: true, 267 | parse-header: false, 268 | parse-formatted-cell: false, 269 | eval-as-markup: false, 270 | locale: none, 271 | ..append-args, 272 | ) = { 273 | // Convert columns to string if it's an array 274 | let columns_str = if type(selected-cols) == array { 275 | selected-cols.map(str).join(",") 276 | } else if selected-cols != none { 277 | str(selected-cols) 278 | } else { 279 | "" 280 | } 281 | 282 | let data = p.to_typst( 283 | xlsx, 284 | bytes(str(sheet-index)), 285 | bytes(if sheet-name != none { sheet-name } else { "" }), 286 | bytes(if parse-alignment { "true" } else { "false" }), 287 | bytes(if parse-stroke { "true" } else { "false" }), 288 | bytes(if parse-fill { "true" } else { "false" }), 289 | bytes(if parse-font { "true" } else { "false" }), 290 | bytes(columns_str), 291 | ) 292 | // toml(data) 293 | parse_excel_table( 294 | if sys.version < version(0, 13, 0) { 295 | toml.decode(data) 296 | } else { 297 | toml(data) 298 | }, 299 | prepend-elems: prepend-elems, 300 | parse-header: parse-header, 301 | parse-table-style: parse-table-style, 302 | parse-stroke: parse-stroke, 303 | parse-formatted-cell: parse-formatted-cell, 304 | eval-as-markup: eval-as-markup, 305 | ..append-args, 306 | ) 307 | } 308 | 309 | /// Parse table pre-parsed by spreet and return the table. Styles in the table will be ignored but the cell content will be kept. Extra arguments can be passed to the table. 310 | /// 311 | /// - dict (dictionary): spreet parsed table. 312 | /// - sheet-index (integer): The index of the sheet to be parsed. 313 | /// - sheet-name (string): The name of the sheet to be parsed (alternative to sheet-index). 314 | /// - args (arguments): Other arguments for the table. 315 | /// -> 316 | #let spreet-parser( 317 | dict, 318 | sheet-index: 0, 319 | sheet-name: none, 320 | ..args, 321 | ) = { 322 | let sheets = dict.keys() 323 | let sheet_index = if sheet-name != none { 324 | sheets.position(s => s == sheet-name) 325 | } else { 326 | sheet-index 327 | } 328 | let cells = dict.at(sheets.at(sheet_index)) 329 | let columns = cells.first().len() 330 | table( 331 | columns: columns, 332 | ..args, 333 | ..cells.flatten().map(cell => table.cell()[#cell]) 334 | ) 335 | } 336 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use typst_wasm_protocol::wasm_export; 2 | 3 | use std::{io::Cursor, str}; 4 | use umya_spreadsheet::{reader, Cell, Spreadsheet}; 5 | mod cell_utils; 6 | mod data_structures; 7 | mod utils; 8 | mod worksheet_utils; 9 | // mod tests; 10 | 11 | use cell_utils::*; 12 | use data_structures::*; 13 | use utils::*; 14 | use worksheet_utils::*; 15 | 16 | #[wasm_export] 17 | pub fn to_typst( 18 | bytes: &[u8], 19 | sheet_index: &[u8], 20 | sheet_name: &[u8], 21 | parse_alignment: &[u8], 22 | parse_border: &[u8], 23 | parse_bg_color: &[u8], 24 | parse_font_style: &[u8], 25 | columns: &[u8], 26 | ) -> Result { 27 | // Parse arguments from byte arrays 28 | let sheet_index_str = 29 | str::from_utf8(sheet_index).map_err(|e| format!("Failed to parse sheet index: {}", e))?; 30 | let sheet_name_str = 31 | str::from_utf8(sheet_name).map_err(|e| format!("Failed to parse sheet name: {}", e))?; 32 | let parse_alignment = parse_arg::(parse_alignment, "parse_alignment")?; 33 | let parse_border = parse_arg::(parse_border, "parse_border")?; 34 | let parse_bg_color = parse_arg::(parse_bg_color, "parse_bg_color")?; 35 | let parse_font_style = parse_arg::(parse_font_style, "parse_font_style")?; 36 | let columns_str = 37 | str::from_utf8(columns).map_err(|e| format!("Failed to parse columns: {}", e))?; 38 | 39 | // Parse columns if provided 40 | let selected_columns = if columns_str.is_empty() { 41 | None 42 | } else { 43 | Some(parse_columns(columns_str)?) 44 | }; 45 | 46 | // Read Excel file 47 | let file = Cursor::new(bytes); 48 | let book: Spreadsheet = reader::xlsx::read_reader(file, true) 49 | .map_err(|e| format!("Failed to read Excel file: {}", e))?; 50 | 51 | let worksheet = if !sheet_name_str.is_empty() { 52 | book.get_sheet_by_name(sheet_name_str) 53 | .ok_or_else(|| format!("Failed to get worksheet by name: {}", sheet_name_str))? 54 | } else { 55 | let index = sheet_index_str 56 | .parse::() 57 | .map_err(|_| format!("Invalid sheet index: {}", sheet_index_str))?; 58 | book.get_sheet(&index) 59 | .ok_or_else(|| format!("Failed to get worksheet by index: {}", index))? 60 | }; 61 | 62 | // Build table data 63 | let table_data = build_table_data( 64 | worksheet, 65 | &book, 66 | parse_alignment, 67 | parse_border, 68 | parse_bg_color, 69 | parse_font_style, 70 | selected_columns.as_ref(), 71 | )?; 72 | 73 | // Convert to TOML 74 | let toml_string = 75 | toml::to_string(&table_data).map_err(|e| format!("Failed to serialize to TOML: {}", e))?; 76 | 77 | Ok(toml_string) 78 | } 79 | 80 | // Helper function to parse arguments from byte arrays 81 | fn parse_arg(arg: &[u8], arg_name: &str) -> Result 82 | where 83 | T::Err: std::fmt::Display, 84 | { 85 | str::from_utf8(arg) 86 | .map_err(|e| format!("Failed to parse {}: {}", arg_name, e))? 87 | .parse() 88 | .map_err(|e| format!("Failed to parse {}: {}", arg_name, e)) 89 | } 90 | 91 | // Parse columns specification like "A,C,F" into column numbers 92 | fn parse_columns(columns_str: &str) -> Result, String> { 93 | let mut columns = Vec::new(); 94 | for col in columns_str.split(',') { 95 | let col = col.trim(); 96 | if col.is_empty() { 97 | continue; 98 | } 99 | let col_num = column_name_to_number(col)?; 100 | columns.push(col_num); 101 | } 102 | Ok(columns) 103 | } 104 | 105 | // Convert Excel column name (A, B, AA, etc.) to column number (1, 2, 27, etc.) 106 | fn column_name_to_number(name: &str) -> Result { 107 | let mut result: u32 = 0; 108 | for c in name.chars() { 109 | if !c.is_ascii_alphabetic() { 110 | return Err(format!("Invalid column name: {}", name)); 111 | } 112 | let c = c.to_ascii_uppercase(); 113 | result = result * 26 + (c as u32 - 'A' as u32 + 1); 114 | } 115 | if result == 0 { 116 | return Err(format!("Invalid column name: {}", name)); 117 | } 118 | Ok(result) 119 | } 120 | 121 | // Function to build the table data structure 122 | fn build_table_data( 123 | worksheet: &umya_spreadsheet::Worksheet, 124 | book: &Spreadsheet, 125 | parse_alignment: bool, 126 | parse_border: bool, 127 | parse_bg_color: bool, 128 | parse_font_style: bool, 129 | selected_columns: Option<&Vec>, 130 | ) -> Result { 131 | let (max_col, max_row) = get_table_dimensions(worksheet)?; 132 | 133 | // Filter columns if specified 134 | let (effective_max_col, column_mapping) = if let Some(cols) = selected_columns { 135 | let _max_selected = cols.iter().max().copied().unwrap_or(max_col); 136 | let mapping: Vec = cols.clone(); 137 | (mapping.len() as u32, Some(mapping)) 138 | } else { 139 | (max_col, None) 140 | }; 141 | 142 | let mut table_data = TableData { 143 | dimensions: TableDimensions { 144 | columns: Vec::new(), 145 | rows: Vec::new(), 146 | max_columns: Some(effective_max_col), 147 | max_rows: Some(max_row), 148 | }, 149 | rows: Vec::new(), 150 | merged_cells: Vec::new(), 151 | }; 152 | 153 | // Process table dimensions 154 | let properties = worksheet.get_sheet_format_properties(); 155 | table_data.dimensions.columns = get_column_widths( 156 | worksheet, 157 | max_col, 158 | *properties.get_default_column_width(), 159 | column_mapping.as_ref(), 160 | ); 161 | table_data.dimensions.rows = 162 | get_row_heights(worksheet, max_row, *properties.get_default_row_height()); 163 | 164 | // Process merged cells 165 | for merge_cell in worksheet.get_merge_cells() { 166 | let range = merge_cell.get_range().to_string(); 167 | let (start, end) = parse_merge_range(&range); 168 | let (start_col, start_row) = parse_cell_reference(&start); 169 | let (end_col, end_row) = parse_cell_reference(&end); 170 | 171 | table_data.merged_cells.push(MergedCell { 172 | range, 173 | start: Position { 174 | row: start_row, 175 | column: start_col, 176 | }, 177 | end: Position { 178 | row: end_row, 179 | column: end_col, 180 | }, 181 | }); 182 | } 183 | 184 | // Process row data 185 | process_rows( 186 | worksheet, 187 | book, 188 | &mut table_data, 189 | max_row, 190 | max_col, 191 | parse_alignment, 192 | parse_border, 193 | parse_bg_color, 194 | parse_font_style, 195 | selected_columns, 196 | &column_mapping, 197 | )?; 198 | 199 | Ok(table_data) 200 | } 201 | 202 | // Process rows and cells 203 | fn process_rows( 204 | worksheet: &umya_spreadsheet::Worksheet, 205 | book: &Spreadsheet, 206 | table_data: &mut TableData, 207 | max_row: u32, 208 | max_col: u32, 209 | parse_alignment: bool, 210 | parse_border: bool, 211 | parse_bg_color: bool, 212 | parse_font_style: bool, 213 | _selected_columns: Option<&Vec>, 214 | column_mapping: &Option>, 215 | ) -> Result<(), String> { 216 | for row_num in 1..=max_row { 217 | let row = worksheet.get_collection_by_row(&row_num); 218 | let mut row_data = RowData { 219 | row_number: row_num, 220 | cells: Vec::new(), 221 | }; 222 | 223 | // Map to store cells by column 224 | let mut col_cell_map: Vec> = vec![None; max_col as usize]; 225 | for cell in row { 226 | let (col_num, _) = parse_cell_reference(&cell.get_coordinate().to_string()); 227 | col_cell_map[(col_num - 1) as usize] = Some(cell); 228 | } 229 | 230 | // Process columns based on mapping 231 | if let Some(mapping) = column_mapping { 232 | // Selected columns mode: map to new column indices 233 | for (new_col_idx, &orig_col_num) in mapping.iter().enumerate() { 234 | let new_col_num = (new_col_idx + 1) as u32; 235 | 236 | // Check if it's a merged cell 237 | let is_merged = is_merged_cell(table_data, row_num, orig_col_num); 238 | 239 | if !is_merged { 240 | if let Some(Some(cell)) = col_cell_map.get((orig_col_num - 1) as usize) { 241 | let cell_style = create_cell_style( 242 | cell, 243 | book, 244 | parse_alignment, 245 | parse_border, 246 | parse_bg_color, 247 | parse_font_style, 248 | ); 249 | 250 | row_data.cells.push(CellData { 251 | data_type: cell_type(cell)?, 252 | format: cell_format_code(cell)?, 253 | value: cell_value(cell)?, 254 | column: new_col_num, 255 | style: cell_style, 256 | }); 257 | } else { 258 | // Empty cell for selected column 259 | row_data.cells.push(CellData { 260 | data_type: "str".to_string(), 261 | format: "General".to_string(), 262 | value: "".to_string(), 263 | column: new_col_num, 264 | style: None, 265 | }); 266 | } 267 | } 268 | } 269 | } else { 270 | // Normal mode: process all columns 271 | for col_num in 1..=max_col { 272 | // Check if it's a merged cell 273 | let is_merged = is_merged_cell(table_data, row_num, col_num); 274 | 275 | if !is_merged { 276 | if let Some(Some(cell)) = col_cell_map.get((col_num - 1) as usize) { 277 | let cell_style = create_cell_style( 278 | cell, 279 | book, 280 | parse_alignment, 281 | parse_border, 282 | parse_bg_color, 283 | parse_font_style, 284 | ); 285 | 286 | row_data.cells.push(CellData { 287 | data_type: cell_type(cell)?, 288 | format: cell_format_code(cell)?, 289 | value: cell_value(cell)?, 290 | column: col_num, 291 | style: cell_style, 292 | }); 293 | } 294 | } 295 | } 296 | } 297 | 298 | if !row_data.cells.is_empty() { 299 | table_data.rows.push(row_data); 300 | } 301 | } 302 | Ok(()) 303 | } 304 | 305 | // Check if cell is part of a merged range (except the top-left cell) 306 | fn is_merged_cell(table_data: &TableData, row_num: u32, col_num: u32) -> bool { 307 | table_data.merged_cells.iter().any(|mc| { 308 | row_num >= mc.start.row 309 | && row_num <= mc.end.row 310 | && col_num >= mc.start.column 311 | && col_num <= mc.end.column 312 | && !(row_num == mc.start.row && col_num == mc.start.column) 313 | }) 314 | } 315 | 316 | // Create cell style based on parsing flags 317 | fn create_cell_style( 318 | cell: &Cell, 319 | book: &Spreadsheet, 320 | parse_alignment: bool, 321 | parse_border: bool, 322 | parse_bg_color: bool, 323 | parse_font_style: bool, 324 | ) -> Option { 325 | if parse_alignment || parse_border || parse_bg_color || parse_font_style { 326 | Some(CellStyle { 327 | alignment: if parse_alignment { 328 | get_cell_alignment(cell) 329 | } else { 330 | None 331 | }, 332 | border: if parse_border { 333 | get_cell_border(cell) 334 | } else { 335 | None 336 | }, 337 | color: if parse_bg_color { 338 | get_cell_bg_color(cell, book) 339 | } else { 340 | None 341 | }, 342 | font: if parse_font_style { 343 | get_cell_font_style(cell, book) 344 | } else { 345 | None 346 | }, 347 | }) 348 | } else { 349 | None 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /crates/xlsx-parser-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 | 11 | [[package]] 12 | name = "aes" 13 | version = "0.8.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 16 | dependencies = [ 17 | "cfg-if", 18 | "cipher", 19 | "cpufeatures", 20 | ] 21 | 22 | [[package]] 23 | name = "ahash" 24 | version = "0.8.12" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 27 | dependencies = [ 28 | "cfg-if", 29 | "getrandom 0.3.4", 30 | "once_cell", 31 | "version_check", 32 | "zerocopy", 33 | ] 34 | 35 | [[package]] 36 | name = "aho-corasick" 37 | version = "1.1.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 40 | dependencies = [ 41 | "memchr", 42 | ] 43 | 44 | [[package]] 45 | name = "android_system_properties" 46 | version = "0.1.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 49 | dependencies = [ 50 | "libc", 51 | ] 52 | 53 | [[package]] 54 | name = "arbitrary" 55 | version = "1.4.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 58 | dependencies = [ 59 | "derive_arbitrary", 60 | ] 61 | 62 | [[package]] 63 | name = "autocfg" 64 | version = "1.5.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 67 | 68 | [[package]] 69 | name = "base64" 70 | version = "0.22.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 73 | 74 | [[package]] 75 | name = "bit-set" 76 | version = "0.8.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" 79 | dependencies = [ 80 | "bit-vec", 81 | ] 82 | 83 | [[package]] 84 | name = "bit-vec" 85 | version = "0.8.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" 88 | 89 | [[package]] 90 | name = "block-buffer" 91 | version = "0.10.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 94 | dependencies = [ 95 | "generic-array", 96 | ] 97 | 98 | [[package]] 99 | name = "block-padding" 100 | version = "0.3.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 103 | dependencies = [ 104 | "generic-array", 105 | ] 106 | 107 | [[package]] 108 | name = "bumpalo" 109 | version = "3.19.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 112 | 113 | [[package]] 114 | name = "byteorder" 115 | version = "1.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 118 | 119 | [[package]] 120 | name = "cbc" 121 | version = "0.1.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 124 | dependencies = [ 125 | "cipher", 126 | ] 127 | 128 | [[package]] 129 | name = "cc" 130 | version = "1.2.45" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" 133 | dependencies = [ 134 | "find-msvc-tools", 135 | "shlex", 136 | ] 137 | 138 | [[package]] 139 | name = "cfb" 140 | version = "0.10.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" 143 | dependencies = [ 144 | "byteorder", 145 | "fnv", 146 | "uuid", 147 | ] 148 | 149 | [[package]] 150 | name = "cfg-if" 151 | version = "1.0.4" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 154 | 155 | [[package]] 156 | name = "chrono" 157 | version = "0.4.42" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 160 | dependencies = [ 161 | "iana-time-zone", 162 | "num-traits", 163 | "windows-link", 164 | ] 165 | 166 | [[package]] 167 | name = "cipher" 168 | version = "0.4.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 171 | dependencies = [ 172 | "crypto-common", 173 | "inout", 174 | ] 175 | 176 | [[package]] 177 | name = "core-foundation-sys" 178 | version = "0.8.7" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 181 | 182 | [[package]] 183 | name = "cpufeatures" 184 | version = "0.2.17" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 187 | dependencies = [ 188 | "libc", 189 | ] 190 | 191 | [[package]] 192 | name = "crc32fast" 193 | version = "1.5.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 196 | dependencies = [ 197 | "cfg-if", 198 | ] 199 | 200 | [[package]] 201 | name = "crossbeam-utils" 202 | version = "0.8.21" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 205 | 206 | [[package]] 207 | name = "crypto-common" 208 | version = "0.1.6" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 211 | dependencies = [ 212 | "generic-array", 213 | "typenum", 214 | ] 215 | 216 | [[package]] 217 | name = "derive_arbitrary" 218 | version = "1.4.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" 221 | dependencies = [ 222 | "proc-macro2", 223 | "quote", 224 | "syn", 225 | ] 226 | 227 | [[package]] 228 | name = "digest" 229 | version = "0.10.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 232 | dependencies = [ 233 | "block-buffer", 234 | "crypto-common", 235 | "subtle", 236 | ] 237 | 238 | [[package]] 239 | name = "displaydoc" 240 | version = "0.2.5" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 243 | dependencies = [ 244 | "proc-macro2", 245 | "quote", 246 | "syn", 247 | ] 248 | 249 | [[package]] 250 | name = "doc-comment" 251 | version = "0.3.4" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" 254 | 255 | [[package]] 256 | name = "encoding_rs" 257 | version = "0.8.35" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 260 | dependencies = [ 261 | "cfg-if", 262 | ] 263 | 264 | [[package]] 265 | name = "equivalent" 266 | version = "1.0.2" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 269 | 270 | [[package]] 271 | name = "fancy-regex" 272 | version = "0.14.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" 275 | dependencies = [ 276 | "bit-set", 277 | "regex-automata", 278 | "regex-syntax", 279 | ] 280 | 281 | [[package]] 282 | name = "find-msvc-tools" 283 | version = "0.1.4" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 286 | 287 | [[package]] 288 | name = "flate2" 289 | version = "1.1.5" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 292 | dependencies = [ 293 | "crc32fast", 294 | "miniz_oxide", 295 | ] 296 | 297 | [[package]] 298 | name = "fnv" 299 | version = "1.0.7" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 302 | 303 | [[package]] 304 | name = "generic-array" 305 | version = "0.14.9" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 308 | dependencies = [ 309 | "typenum", 310 | "version_check", 311 | ] 312 | 313 | [[package]] 314 | name = "getrandom" 315 | version = "0.2.16" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 318 | dependencies = [ 319 | "cfg-if", 320 | "js-sys", 321 | "libc", 322 | "wasi", 323 | "wasm-bindgen", 324 | ] 325 | 326 | [[package]] 327 | name = "getrandom" 328 | version = "0.3.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 331 | dependencies = [ 332 | "cfg-if", 333 | "js-sys", 334 | "libc", 335 | "r-efi", 336 | "wasip2", 337 | "wasm-bindgen", 338 | ] 339 | 340 | [[package]] 341 | name = "hashbrown" 342 | version = "0.16.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 345 | 346 | [[package]] 347 | name = "hmac" 348 | version = "0.12.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 351 | dependencies = [ 352 | "digest", 353 | ] 354 | 355 | [[package]] 356 | name = "html_parser" 357 | version = "0.7.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd" 360 | dependencies = [ 361 | "doc-comment", 362 | "pest", 363 | "pest_derive", 364 | "serde", 365 | "serde_derive", 366 | "serde_json", 367 | "thiserror 1.0.69", 368 | ] 369 | 370 | [[package]] 371 | name = "iana-time-zone" 372 | version = "0.1.64" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 375 | dependencies = [ 376 | "android_system_properties", 377 | "core-foundation-sys", 378 | "iana-time-zone-haiku", 379 | "js-sys", 380 | "log", 381 | "wasm-bindgen", 382 | "windows-core", 383 | ] 384 | 385 | [[package]] 386 | name = "iana-time-zone-haiku" 387 | version = "0.1.2" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 390 | dependencies = [ 391 | "cc", 392 | ] 393 | 394 | [[package]] 395 | name = "imagesize" 396 | version = "0.14.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" 399 | 400 | [[package]] 401 | name = "indexmap" 402 | version = "2.12.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 405 | dependencies = [ 406 | "equivalent", 407 | "hashbrown", 408 | ] 409 | 410 | [[package]] 411 | name = "inout" 412 | version = "0.1.4" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 415 | dependencies = [ 416 | "block-padding", 417 | "generic-array", 418 | ] 419 | 420 | [[package]] 421 | name = "itoa" 422 | version = "1.0.15" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 425 | 426 | [[package]] 427 | name = "js-sys" 428 | version = "0.3.82" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" 431 | dependencies = [ 432 | "once_cell", 433 | "wasm-bindgen", 434 | ] 435 | 436 | [[package]] 437 | name = "lazy_static" 438 | version = "1.5.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 441 | 442 | [[package]] 443 | name = "libc" 444 | version = "0.2.177" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 447 | 448 | [[package]] 449 | name = "log" 450 | version = "0.4.28" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 453 | 454 | [[package]] 455 | name = "md-5" 456 | version = "0.10.6" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 459 | dependencies = [ 460 | "cfg-if", 461 | "digest", 462 | ] 463 | 464 | [[package]] 465 | name = "memchr" 466 | version = "2.7.6" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 469 | 470 | [[package]] 471 | name = "miniz_oxide" 472 | version = "0.8.9" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 475 | dependencies = [ 476 | "adler2", 477 | "simd-adler32", 478 | ] 479 | 480 | [[package]] 481 | name = "num-traits" 482 | version = "0.2.19" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 485 | dependencies = [ 486 | "autocfg", 487 | ] 488 | 489 | [[package]] 490 | name = "once_cell" 491 | version = "1.21.3" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 494 | 495 | [[package]] 496 | name = "pest" 497 | version = "2.8.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" 500 | dependencies = [ 501 | "memchr", 502 | "ucd-trie", 503 | ] 504 | 505 | [[package]] 506 | name = "pest_derive" 507 | version = "2.8.3" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" 510 | dependencies = [ 511 | "pest", 512 | "pest_generator", 513 | ] 514 | 515 | [[package]] 516 | name = "pest_generator" 517 | version = "2.8.3" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" 520 | dependencies = [ 521 | "pest", 522 | "pest_meta", 523 | "proc-macro2", 524 | "quote", 525 | "syn", 526 | ] 527 | 528 | [[package]] 529 | name = "pest_meta" 530 | version = "2.8.3" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" 533 | dependencies = [ 534 | "pest", 535 | "sha2", 536 | ] 537 | 538 | [[package]] 539 | name = "proc-macro2" 540 | version = "1.0.103" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 543 | dependencies = [ 544 | "unicode-ident", 545 | ] 546 | 547 | [[package]] 548 | name = "quick-xml" 549 | version = "0.37.5" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" 552 | dependencies = [ 553 | "memchr", 554 | "serde", 555 | ] 556 | 557 | [[package]] 558 | name = "quote" 559 | version = "1.0.42" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 562 | dependencies = [ 563 | "proc-macro2", 564 | ] 565 | 566 | [[package]] 567 | name = "r-efi" 568 | version = "5.3.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 571 | 572 | [[package]] 573 | name = "regex" 574 | version = "1.12.2" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 577 | dependencies = [ 578 | "aho-corasick", 579 | "memchr", 580 | "regex-automata", 581 | "regex-syntax", 582 | ] 583 | 584 | [[package]] 585 | name = "regex-automata" 586 | version = "0.4.13" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 589 | dependencies = [ 590 | "aho-corasick", 591 | "memchr", 592 | "regex-syntax", 593 | ] 594 | 595 | [[package]] 596 | name = "regex-syntax" 597 | version = "0.8.8" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 600 | 601 | [[package]] 602 | name = "rustversion" 603 | version = "1.0.22" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 606 | 607 | [[package]] 608 | name = "ryu" 609 | version = "1.0.20" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 612 | 613 | [[package]] 614 | name = "serde" 615 | version = "1.0.228" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 618 | dependencies = [ 619 | "serde_core", 620 | "serde_derive", 621 | ] 622 | 623 | [[package]] 624 | name = "serde_core" 625 | version = "1.0.228" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 628 | dependencies = [ 629 | "serde_derive", 630 | ] 631 | 632 | [[package]] 633 | name = "serde_derive" 634 | version = "1.0.228" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 637 | dependencies = [ 638 | "proc-macro2", 639 | "quote", 640 | "syn", 641 | ] 642 | 643 | [[package]] 644 | name = "serde_json" 645 | version = "1.0.145" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 648 | dependencies = [ 649 | "itoa", 650 | "memchr", 651 | "ryu", 652 | "serde", 653 | "serde_core", 654 | ] 655 | 656 | [[package]] 657 | name = "serde_spanned" 658 | version = "1.0.3" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 661 | dependencies = [ 662 | "serde_core", 663 | ] 664 | 665 | [[package]] 666 | name = "sha2" 667 | version = "0.10.9" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 670 | dependencies = [ 671 | "cfg-if", 672 | "cpufeatures", 673 | "digest", 674 | ] 675 | 676 | [[package]] 677 | name = "shlex" 678 | version = "1.3.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 681 | 682 | [[package]] 683 | name = "simd-adler32" 684 | version = "0.3.7" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 687 | 688 | [[package]] 689 | name = "subtle" 690 | version = "2.6.1" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 693 | 694 | [[package]] 695 | name = "syn" 696 | version = "2.0.110" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 699 | dependencies = [ 700 | "proc-macro2", 701 | "quote", 702 | "unicode-ident", 703 | ] 704 | 705 | [[package]] 706 | name = "thin-vec" 707 | version = "0.2.14" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" 710 | 711 | [[package]] 712 | name = "thiserror" 713 | version = "1.0.69" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 716 | dependencies = [ 717 | "thiserror-impl 1.0.69", 718 | ] 719 | 720 | [[package]] 721 | name = "thiserror" 722 | version = "2.0.17" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 725 | dependencies = [ 726 | "thiserror-impl 2.0.17", 727 | ] 728 | 729 | [[package]] 730 | name = "thiserror-impl" 731 | version = "1.0.69" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 734 | dependencies = [ 735 | "proc-macro2", 736 | "quote", 737 | "syn", 738 | ] 739 | 740 | [[package]] 741 | name = "thiserror-impl" 742 | version = "2.0.17" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 745 | dependencies = [ 746 | "proc-macro2", 747 | "quote", 748 | "syn", 749 | ] 750 | 751 | [[package]] 752 | name = "thousands" 753 | version = "0.2.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" 756 | 757 | [[package]] 758 | name = "toml" 759 | version = "0.9.8" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 762 | dependencies = [ 763 | "indexmap", 764 | "serde_core", 765 | "serde_spanned", 766 | "toml_datetime", 767 | "toml_parser", 768 | "toml_writer", 769 | "winnow", 770 | ] 771 | 772 | [[package]] 773 | name = "toml_datetime" 774 | version = "0.7.3" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" 777 | dependencies = [ 778 | "serde_core", 779 | ] 780 | 781 | [[package]] 782 | name = "toml_parser" 783 | version = "1.0.4" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" 786 | dependencies = [ 787 | "winnow", 788 | ] 789 | 790 | [[package]] 791 | name = "toml_writer" 792 | version = "1.0.4" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" 795 | 796 | [[package]] 797 | name = "typenum" 798 | version = "1.19.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 801 | 802 | [[package]] 803 | name = "typst-wasm-macros" 804 | version = "0.0.2" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "0df89cf4e273dca9ca1a971f86d5bc12f7ec79d008fbc11037dbc8f821196316" 807 | dependencies = [ 808 | "proc-macro2", 809 | "quote", 810 | "syn", 811 | "venial", 812 | ] 813 | 814 | [[package]] 815 | name = "typst-wasm-protocol" 816 | version = "0.0.2" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "1e74aabca9c7bb93eb529915add290b6cc11231f3fb36f2c6080007a658699df" 819 | dependencies = [ 820 | "typst-wasm-macros", 821 | ] 822 | 823 | [[package]] 824 | name = "ucd-trie" 825 | version = "0.1.7" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 828 | 829 | [[package]] 830 | name = "umya-spreadsheet" 831 | version = "2.3.3" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "408c7e039c96ec1d517a1111ade7fadab889f32c096dac691a1e3b8018c3e39a" 834 | dependencies = [ 835 | "aes", 836 | "ahash", 837 | "base64", 838 | "byteorder", 839 | "cbc", 840 | "cfb", 841 | "chrono", 842 | "encoding_rs", 843 | "fancy-regex", 844 | "getrandom 0.2.16", 845 | "hmac", 846 | "html_parser", 847 | "imagesize", 848 | "lazy_static", 849 | "md-5", 850 | "quick-xml", 851 | "regex", 852 | "sha2", 853 | "thin-vec", 854 | "thousands", 855 | "zip", 856 | ] 857 | 858 | [[package]] 859 | name = "unicode-ident" 860 | version = "1.0.22" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 863 | 864 | [[package]] 865 | name = "uuid" 866 | version = "1.18.1" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" 869 | dependencies = [ 870 | "js-sys", 871 | "wasm-bindgen", 872 | ] 873 | 874 | [[package]] 875 | name = "venial" 876 | version = "0.6.1" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "9a42528baceab6c7784446df2a10f4185078c39bf73dc614f154353f1a6b1229" 879 | dependencies = [ 880 | "proc-macro2", 881 | "quote", 882 | ] 883 | 884 | [[package]] 885 | name = "version_check" 886 | version = "0.9.5" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 889 | 890 | [[package]] 891 | name = "wasi" 892 | version = "0.11.1+wasi-snapshot-preview1" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 895 | 896 | [[package]] 897 | name = "wasip2" 898 | version = "1.0.1+wasi-0.2.4" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 901 | dependencies = [ 902 | "wit-bindgen", 903 | ] 904 | 905 | [[package]] 906 | name = "wasm-bindgen" 907 | version = "0.2.105" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 910 | dependencies = [ 911 | "cfg-if", 912 | "once_cell", 913 | "rustversion", 914 | "wasm-bindgen-macro", 915 | "wasm-bindgen-shared", 916 | ] 917 | 918 | [[package]] 919 | name = "wasm-bindgen-macro" 920 | version = "0.2.105" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" 923 | dependencies = [ 924 | "quote", 925 | "wasm-bindgen-macro-support", 926 | ] 927 | 928 | [[package]] 929 | name = "wasm-bindgen-macro-support" 930 | version = "0.2.105" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" 933 | dependencies = [ 934 | "bumpalo", 935 | "proc-macro2", 936 | "quote", 937 | "syn", 938 | "wasm-bindgen-shared", 939 | ] 940 | 941 | [[package]] 942 | name = "wasm-bindgen-shared" 943 | version = "0.2.105" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" 946 | dependencies = [ 947 | "unicode-ident", 948 | ] 949 | 950 | [[package]] 951 | name = "windows-core" 952 | version = "0.62.2" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 955 | dependencies = [ 956 | "windows-implement", 957 | "windows-interface", 958 | "windows-link", 959 | "windows-result", 960 | "windows-strings", 961 | ] 962 | 963 | [[package]] 964 | name = "windows-implement" 965 | version = "0.60.2" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 968 | dependencies = [ 969 | "proc-macro2", 970 | "quote", 971 | "syn", 972 | ] 973 | 974 | [[package]] 975 | name = "windows-interface" 976 | version = "0.59.3" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 979 | dependencies = [ 980 | "proc-macro2", 981 | "quote", 982 | "syn", 983 | ] 984 | 985 | [[package]] 986 | name = "windows-link" 987 | version = "0.2.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 990 | 991 | [[package]] 992 | name = "windows-result" 993 | version = "0.4.1" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 996 | dependencies = [ 997 | "windows-link", 998 | ] 999 | 1000 | [[package]] 1001 | name = "windows-strings" 1002 | version = "0.5.1" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 1005 | dependencies = [ 1006 | "windows-link", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "winnow" 1011 | version = "0.7.13" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 1014 | 1015 | [[package]] 1016 | name = "wit-bindgen" 1017 | version = "0.46.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 1020 | 1021 | [[package]] 1022 | name = "xlsx-parser-rs" 1023 | version = "0.3.4" 1024 | dependencies = [ 1025 | "getrandom 0.3.4", 1026 | "once_cell", 1027 | "serde", 1028 | "toml", 1029 | "typst-wasm-protocol", 1030 | "umya-spreadsheet", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "zerocopy" 1035 | version = "0.8.27" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 1038 | dependencies = [ 1039 | "zerocopy-derive", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "zerocopy-derive" 1044 | version = "0.8.27" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 1047 | dependencies = [ 1048 | "proc-macro2", 1049 | "quote", 1050 | "syn", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "zip" 1055 | version = "2.4.2" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" 1058 | dependencies = [ 1059 | "arbitrary", 1060 | "crc32fast", 1061 | "crossbeam-utils", 1062 | "displaydoc", 1063 | "flate2", 1064 | "indexmap", 1065 | "memchr", 1066 | "thiserror 2.0.17", 1067 | "zopfli", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "zopfli" 1072 | version = "0.8.3" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" 1075 | dependencies = [ 1076 | "bumpalo", 1077 | "crc32fast", 1078 | "log", 1079 | "simd-adler32", 1080 | ] 1081 | --------------------------------------------------------------------------------