├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── feature_request.yml │ └── question.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── rust_clippy.yml │ ├── rust_default_tests.yml │ ├── rust_docs.yml │ └── rust_fmt.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE_Apache2.0 ├── LICENSE_MIT ├── README.md ├── examples ├── Readme.md ├── doc_write_excel_add_worksheet.rs ├── doc_write_excel_autofit.rs ├── doc_write_excel_chart.rs ├── doc_write_excel_combined.rs ├── doc_write_excel_date_format.rs ├── doc_write_excel_datetime_format.rs ├── doc_write_excel_float_format.rs ├── doc_write_excel_float_precision.rs ├── doc_write_excel_intro.rs ├── doc_write_excel_null_values.rs ├── doc_write_excel_set_column_format.rs ├── doc_write_excel_set_freeze_panes.rs ├── doc_write_excel_set_freeze_panes_top_cell.rs ├── doc_write_excel_set_header.rs ├── doc_write_excel_set_header_format.rs ├── doc_write_excel_set_nan_value.rs ├── doc_write_excel_set_screen_gridlines.rs ├── doc_write_excel_set_table.rs ├── doc_write_excel_set_worksheet_name.rs ├── doc_write_excel_set_zoom.rs ├── doc_write_excel_time_format.rs ├── doc_write_excel_worksheet.rs ├── doc_write_excel_write_dataframe.rs ├── doc_write_excel_write_dataframe_to_cell.rs ├── perf_test.py └── perf_test.rs ├── src ├── changelog.rs ├── excel_writer.rs └── lib.rs └── tests ├── input ├── dataframe01.xlsx ├── dataframe02.xlsx ├── dataframe03.xlsx ├── dataframe04.xlsx ├── dataframe05.xlsx ├── dataframe06.xlsx ├── dataframe07.xlsx ├── dataframe08.xlsx ├── dataframe09.xlsx ├── dataframe10.xlsx ├── dataframe11.xlsx ├── dataframe12.xlsx ├── dataframe13.xlsx ├── dataframe14.xlsx ├── dataframe15.xlsx ├── dataframe16.xlsx ├── dataframe17.xlsx ├── dataframe18.xlsx ├── dataframe19.xlsx └── dataframe20.xlsx ├── integration ├── common │ └── mod.rs ├── dataframe01.rs ├── dataframe02.rs ├── dataframe03.rs ├── dataframe04.rs ├── dataframe05.rs ├── dataframe06.rs ├── dataframe07.rs ├── dataframe08.rs ├── dataframe09.rs ├── dataframe10.rs ├── dataframe11.rs ├── dataframe12.rs ├── dataframe13.rs ├── dataframe14.rs ├── dataframe15.rs ├── dataframe16.rs ├── dataframe17.rs ├── dataframe18.rs ├── dataframe19.rs ├── dataframe20.rs └── main.rs └── output └── .gitignore /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["paypal.me/xlsxwriter"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug 2 | 3 | description: File a bug/issue in polars_excel_writer 4 | 5 | title: "Bug: " 6 | 7 | labels: [bug] 8 | 9 | body: 10 | 11 | - type: markdown 12 | attributes: 13 | value: Please fill in the title above and the sections below to submit your bug report. 14 | 15 | - type: textarea 16 | attributes: 17 | label: Current behavior 18 | description: A description of what you're experiencing. 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | attributes: 24 | label: Expected behavior 25 | description: A description of what you expected to happen. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | attributes: 31 | label: Sample code to reproduce 32 | description: Please add a small, complete, sample program that demonstrates your issue. It should include a dataframe. 33 | value: | 34 | ```rust 35 | use polars::prelude::*; 36 | 37 | use polars_excel_writer::PolarsExcelWriter; 38 | 39 | fn main() -> PolarsResult<()> { 40 | // Create a sample dataframe for the example. 41 | let df: DataFrame = df!( 42 | "String" => &["North", "South", "East", "West"], 43 | "Integer" => &[1, 2, 3, 4], 44 | "Float" => &[4.0, 5.0, 6.0, 7.0], 45 | )?; 46 | 47 | // Create a new Excel writer. 48 | let mut excel_writer = PolarsExcelWriter::new(); 49 | 50 | // Write the dataframe to Excel. 51 | excel_writer.write_dataframe(&df)?; 52 | 53 | // Save the file to disk. 54 | excel_writer.save("dataframe.xlsx")?; 55 | 56 | Ok(()) 57 | } 58 | ``` 59 | render: markdown 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | attributes: 65 | label: Environment 66 | description: | 67 | Any relevant version or system information: 68 | value: | 69 | - polars_excel_writer version: 70 | - rustc version: 71 | - Excel version: 72 | - OS: 73 | render: markdown 74 | validations: 75 | required: false 76 | 77 | 78 | - type: textarea 79 | attributes: 80 | label: Any other information 81 | description: | 82 | Anything that will give more context about the issue you are encountering. 83 | 84 | Tip: You can attach images by clicking this area to highlight it and then dragging files in. 85 | validations: 86 | required: false 87 | 88 | 89 | - type: markdown 90 | attributes: 91 | value: | 92 | **Note** for OpenOffice and LibreOffice users: Please verify that the 93 | issue being reported also happens in Excel. 94 | 95 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🎯 Feature Request 2 | 3 | description: Request a new feature in polars_excel_writer 4 | 5 | title: "feature request: " 6 | 7 | labels: [feature request] 8 | 9 | body: 10 | 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Use the dialog box below to request a new feature in polars_excel_writer. 15 | 16 | - type: textarea 17 | attributes: 18 | label: Feature Request 19 | description: | 20 | Describe the new feature that you would like to see in polars_excel_writer. If the feature isn't obvious consider 21 | adding a screenshot to help explain the request. 22 | 23 | You can also check the [Roadmap](https://github.com/jmcnamara/polars_excel_writer/issues/1) to see when/if the feature is planned. 24 | validations: 25 | required: true 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: ❓ Question 2 | 3 | description: Ask a question about polars_excel_writer 4 | 5 | title: "question: " 6 | 7 | labels: [question] 8 | 9 | body: 10 | 11 | - type: markdown 12 | attributes: 13 | value: | 14 | General questions on how to do something with the polars_excel_writer crate should be asked on 15 | [StackOverflow](http://stackoverflow.com/questions/tagged/rust_xlsxwriter) with a tag of "rust_xlsxwriter". 16 | This has a better chance of getting several answers and also helps others who might have similar questions in 17 | the future. 18 | 19 | Other questions can be asked below. 20 | 21 | - type: textarea 22 | attributes: 23 | label: Question 24 | description: Ask a question that doesn't belong on StackOverflow 25 | validations: 26 | required: true 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Pull Requests and Contributing to polars_excel_writer 3 | 4 | All patches and pull requests are welcome but must start with an issue tracker. 5 | 6 | 7 | ### Getting Started 8 | 9 | If the change is small such as a documentation or syntax fix then submit the change without the steps below. 10 | 11 | For anything else follow the steps below: 12 | 13 | 1. Pull requests and new feature proposals must start with an [Issue Tracker](https://github.com/jmcnamara/polars_excel_writer/issues). This serves as the focal point for the design discussion. Start with the Question or Feature Request template. 14 | 2. Describe what you plan to do. If there are API changes or additions add some pseudo-code to demonstrate them. 15 | 3. Wait for a response (it should come quickly) before submitting a PR. 16 | 17 | Once there has been some discussion in the Issue Tracker you can prepare the PR as follows: 18 | 19 | 1. Fork the repository. 20 | 2. Run all the tests to make sure the current code work on your system using `cargo test`. 21 | 3. Create a feature branch for your new feature. 22 | 4. Code style is `rustfmt`. 23 | 5. Rebase changes into one commit unless it requires separate logical steps, see below. 24 | 25 | 26 | ### Writing Tests 27 | 28 | Where possible add unit tests in the `src/*.rs` files. 29 | 30 | Integration tests in the `tests` folder are harder to create since the generally require an input Excel 2007 31 | file to test against. The maintainer can help with this and it can be discussed in the Issue Tracker. 32 | 33 | ### Example programs 34 | 35 | If applicable add an example program to the `examples` directory. 36 | 37 | ### Copyright and License 38 | 39 | Copyright remains with the original author. Do not include additional copyright claims or Licensing requirements. GitHub and the `git` repository will record your contribution. 40 | 41 | 42 | ### Submitting the Pull Request 43 | 44 | Follow the commit message style of the commit log which is roughly Linux kernel style. 45 | 46 | If your change involves several incremental `git` commits then `rebase` or `squash` them onto another branch so that the Pull Request is a single commit or a small number of logical commits. 47 | 48 | Push your changes to GitHub and submit the Pull Request with a hash link to the to the Issue tracker that was opened above. 49 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/rust_clippy.yml: -------------------------------------------------------------------------------- 1 | name: Rust - test clippy 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Test for any clippy warnings 21 | run: cargo clippy --all-targets -- -Dwarnings 22 | 23 | - name: Test a subset of clippy pedantic 24 | run: cargo clippy -- -W clippy::pedantic -A clippy::cast_possible_truncation -A clippy::cast_sign_loss -A clippy::single_match_else -A clippy::float_cmp -A clippy::missing_panics_doc -A clippy::must_use_candidate -A clippy::needless_pass_by_value -A clippy::struct_excessive_bools -A clippy::module_name_repetitions -A clippy::return_self_not_must_use -Dwarnings 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/rust_default_tests.yml: -------------------------------------------------------------------------------- 1 | name: Rust - test default feature 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Free some extra build space on test instance. 21 | run: | 22 | sudo rm -rf /usr/share/dotnet 23 | sudo rm -rf /opt/ghc 24 | sudo rm -rf /usr/local/share/boost 25 | sudo rm -rf "$AGENT_TOOLSDIRECTORY" 26 | sudo rm -rf /usr/local/lib/android 27 | sudo rm -rf /opt/hostedtoolcache/CodeQL 28 | 29 | - name: Build 30 | run: cargo build --verbose 31 | 32 | - name: Run the tests for the default feature set 33 | run: cargo test 34 | -------------------------------------------------------------------------------- /.github/workflows/rust_docs.yml: -------------------------------------------------------------------------------- 1 | name: Rust - test docs 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Test doc build for warnings 21 | run: RUSTDOCFLAGS="-D warnings" cargo doc 22 | -------------------------------------------------------------------------------- /.github/workflows/rust_fmt.yml: -------------------------------------------------------------------------------- 1 | name: Rust - test formatting 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Test that new code is formatted 21 | run: | 22 | cargo fmt 23 | git status | grep 'nothing to commit' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .DS_Store 4 | .vscode 5 | *.xlsx 6 | !tests/input/*.xlsx 7 | *.bak 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `polars_excel_writer` will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.14.0] - 2025-05-03 9 | 10 | **Deprecation Notice**: The next version of this crate will drop support for the 11 | Polars `SerWriter` interface or move it to another crate in order to maximize 12 | compatibility with the Polars `write_excel` interface. This may also break 13 | backward compatibility with some APIs or interfaces. See the 14 | [`polars_excel_writer` Roadmap]. 15 | 16 | [`polars_excel_writer` Roadmap]: https://github.com/jmcnamara/polars_excel_writer/issues/1 17 | 18 | ### Added 19 | 20 | - Added support for setting dataframe formatting based on data types or columns. 21 | It also adds header formatting. See: 22 | 23 | - `PolarsExcelWriter::set_dtype_format()` 24 | - `PolarsExcelWriter::set_column_format()` 25 | - `PolarsExcelWriter::set_header_format()` 26 | 27 | ### Deprecated 28 | 29 | - The following functions are deprecated in favour of 30 | `PolarsExcelWriter::set_dtype_format()` and variants: 31 | 32 | - `PolarsExcelWriter::set_float_format()` 33 | - `PolarsExcelWriter::set_time_format()` 34 | - `PolarsExcelWriter::set_date_format()` 35 | - `PolarsExcelWriter::set_datetime_format()` 36 | 37 | 38 | ## [0.13.0] - 2025-03-15 39 | 40 | ### Added 41 | 42 | - Update dependencies to `rust_xlsxwriter` 0.84. 43 | 44 | 45 | ## [0.12.0] - 2025-01-29 46 | 47 | ### Added 48 | 49 | - Update dependencies to `rust_xlsxwriter` 0.82.0 and `polars` 0.46. 50 | 51 | - Added support for overriding the default handling of NaN and Infinity numbers. 52 | These aren't supported by Excel so they are replaced with default or custom 53 | string values. See: 54 | 55 | - `PolarsExcelWriter::set_nan_value()` 56 | - `PolarsExcelWriter::set_infinity_value()` 57 | - `PolarsExcelWriter::set_neg_infinity_value()` 58 | 59 | 60 | ## [0.11.0] - 2025-01-18 61 | 62 | ### Added 63 | 64 | - Update dependencies to `rust_xlsxwriter` 0.81.0 and `polars` 0.45. 65 | 66 | 67 | ## [0.10.0] - 2025-01-05 68 | 69 | ### Added 70 | 71 | - Update dependencies to `rust_xlsxwriter` 0.80.0 and `polars` 0.44. 72 | 73 | - Changed documentation to highlight `write_xlsx` as the primary interface, 74 | since that will be the main interface in future releases. 75 | 76 | 77 | ## [0.9.0] - 2024-09-18 78 | 79 | ### Added 80 | 81 | - Update dependencies to `rust_xlsxwriter` 0.77.0 and `polars` 0.43. 82 | 83 | 84 | ## [0.8.0] - 2024-08-24 85 | 86 | ### Added 87 | 88 | - Update dependencies to `rust_xlsxwriter` 0.74.0 and `polars` 0.42.0. 89 | 90 | 91 | ## [0.7.0] - 2024-03-25 92 | 93 | ### Added 94 | 95 | - Update dependencies to `rust_xlsxwriter` 0.63.0 and `polars` 0.37.0. 96 | 97 | 98 | ## [0.6.0] - 2024-01-24 99 | 100 | ### Added 101 | 102 | - Update dependencies to `rust_xlsxwriter` 0.62.0 and `polars` 0.36.2. 103 | 104 | 105 | ## [0.5.0] - 2024-01-15 106 | 107 | ### Added 108 | 109 | - Added support for writing `u64` and `i64` number within Excel's limitations. 110 | This implies a loss of precision outside Excel's integer range of +/- 111 | 999,999,999,999,999 (15 digits). 112 | 113 | 114 | ## [0.4.0] - 2023-11-22 115 | 116 | ### Added 117 | 118 | - Update to the latest `rust_xlsxwriter` to fix issues with `PolarsError` type/location. 119 | 120 | 121 | ## [0.3.0] - 2023-09-05 122 | 123 | ### Added 124 | 125 | More worksheet utility methods. 126 | 127 | - Added support for renaming worksheets via the [`set_worksheet_name()`] method. 128 | 129 | - Added support for adding worksheets via the [`add_worksheet()`] method. This 130 | allows you to add dataframes to several different worksheets in a workbook. 131 | 132 | - Added support for accessing the current worksheets via the [`worksheet()`] method. 133 | 134 | [`set_worksheet_name()`]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/excel_writer/struct.PolarsExcelWriter.html#method.set_worksheet_name 135 | 136 | [`add_worksheet()`]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/excel_writer/struct.PolarsExcelWriter.html#method.add_worksheet 137 | 138 | [`worksheet()`]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/excel_writer/struct.PolarsExcelWriter.html#method.worksheet 139 | 140 | 141 | ## [0.2.0] - 2023-09-04 142 | 143 | ### Added 144 | 145 | - Added support for setting worksheet table properties via the `PolarsExcelWriter` 146 | [`set_table()`] method. 147 | 148 | [`set_table()`]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/excel_writer/struct.PolarsExcelWriter.html#method.set_table 149 | 150 | ## [0.1.0] - 2023-08-20 151 | 152 | ### Added 153 | 154 | - First functional version. 155 | 156 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polars_excel_writer" 3 | description = "A Polars extension to serialize dataframes to Excel xlsx files" 4 | authors = ["John McNamara "] 5 | repository = "https://github.com/jmcnamara/polars_excel_writer" 6 | keywords = ["polars", "excel", "xlsx"] 7 | readme = "README.md" 8 | license = "MIT OR Apache-2.0" 9 | version = "0.14.0" 10 | edition = "2021" 11 | 12 | 13 | [dependencies] 14 | chrono = "0.4.39" 15 | polars = {version = "0.46", features = ["lazy"]} 16 | polars-arrow = {version = "0.46"} 17 | rust_xlsxwriter = {version = "0.84", features = ["chrono", "polars"]} 18 | 19 | 20 | [dev-dependencies] 21 | zip = {version = "2.2.2", default-features = false, features = ["deflate"]} 22 | regex = "1.11.1" 23 | pretty_assertions = "1.4.1" 24 | 25 | [features] 26 | # `default`: Includes all the standard functionality. 27 | default = [] 28 | 29 | # `zlib`: Adds dependency on zlib and a C compiler. This includes the same 30 | # features as `default` but is 1.5x faster for large files. 31 | zlib = ["zip/deflate-zlib"] 32 | 33 | [package.metadata.commands] 34 | # Some local package management and release check commands. 35 | # Uses Cargo Commander. 36 | 37 | spellcheck = {cmd = [ 38 | "for f in src/*.rs; do aspell --lang=en_US --check $f; done", 39 | "for f in examples/*.rs; do aspell --lang=en_US --check $f; done", 40 | "aspell --lang=en_US --check README.md" 41 | ]} 42 | -------------------------------------------------------------------------------- /LICENSE_Apache2.0: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022-2025 John McNamara 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 John McNamara 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # polars_excel_writer 2 | 3 | The `polars_excel_writer` crate is a library for serializing Polars dataframes 4 | to Excel Xlsx files. 5 | 6 | The crate uses [`rust_xlsxwriter`] to do the Excel serialization and is 7 | typically 5x faster than Polars when exporting large dataframes to Excel. 8 | 9 | It provides a primary interface [`PolarsExcelWriter`] which is a configurable 10 | Excel serializer that resembles the interface options provided by the Polars 11 | [`write_excel()`] dataframe method. 12 | 13 | 14 | [`PolarsExcelWriter`]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/excel_writer/struct.PolarsExcelWriter.html 15 | 16 | [`SerWriter`]: 17 | https://docs.rs/polars/latest/polars/prelude/trait.SerWriter.html 18 | 19 | [`rust_xlsxwriter`]: https://docs.rs/rust_xlsxwriter/latest/rust_xlsxwriter/ 20 | 21 | [`write_excel()`]: 22 | https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_excel.html#polars.DataFrame.write_excel 23 | 24 | ## Example 25 | 26 | An example of writing a Polar Rust dataframe to an Excel file using the 27 | `PolarsExcelWriter` interface. 28 | 29 | ```rust 30 | use chrono::prelude::*; 31 | use polars::prelude::*; 32 | 33 | use polars_excel_writer::PolarsExcelWriter; 34 | 35 | fn main() -> PolarsResult<()> { 36 | // Create a sample dataframe for the example. 37 | let df: DataFrame = df!( 38 | "String" => &["North", "South", "East", "West"], 39 | "Integer" => &[1, 2, 3, 4], 40 | "Float" => &[4.0, 5.0, 6.0, 7.0], 41 | "Time" => &[ 42 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 43 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 44 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 45 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 46 | ], 47 | "Date" => &[ 48 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(), 49 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap(), 50 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap(), 51 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap(), 52 | ], 53 | "Datetime" => &[ 54 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap(), 55 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap().and_hms_opt(2, 0, 0).unwrap(), 56 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap().and_hms_opt(3, 0, 0).unwrap(), 57 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap().and_hms_opt(4, 0, 0).unwrap(), 58 | ], 59 | )?; 60 | 61 | // Create a new Excel writer. 62 | let mut excel_writer = PolarsExcelWriter::new(); 63 | 64 | // Write the dataframe to Excel. 65 | excel_writer.write_dataframe(&df)?; 66 | 67 | // Save the file to disk. 68 | excel_writer.save("dataframe.xlsx")?; 69 | 70 | Ok(()) 71 | } 72 | ``` 73 | 74 | Output file: 75 | 76 | 77 | 78 | 79 | ## Performance 80 | 81 | The table below shows the performance of writing a dataframe using Python 82 | Polars, Python Pandas and `PolarsExcelWriter`. 83 | 84 | | Test Case | Time (s) | Relative (%) | 85 | | :---------------------------- | :------- | :----------- | 86 | | `Polars` | 6.49 | 100% | 87 | | `Pandas` | 10.92 | 168% | 88 | | `polars_excel_writer` | 1.22 | 19% | 89 | | `polars_excel_writer` + `zlib`| 1.08 | 17% | 90 | 91 | See the [Performance] section of the docs for more detail. 92 | 93 | [Performance]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/#performance 94 | 95 | ## See also 96 | 97 | - [The `polars_excel_writer` crate]. 98 | - [The `polars_excel_writer` API docs at docs.rs]. 99 | - [The `polars_excel_writer` repository]. 100 | - [Roadmap of planned features]. 101 | 102 | [The `polars_excel_writer` crate]: https://crates.io/crates/polars_excel_writer 103 | [The `polars_excel_writer` API docs at docs.rs]: https://docs.rs/polars_excel_writer/latest/polars_excel_writer/ 104 | [The `polars_excel_writer` repository]: https://github.com/jmcnamara/polars_excel_writer 105 | [Release Notes and Changelog]: https://github.com/jmcnamara/polars_excel_writer/blob/main/CHANGELOG.md 106 | [Roadmap of planned features]: https://github.com/jmcnamara/polars_excel_writer/issues/1 -------------------------------------------------------------------------------- /examples/Readme.md: -------------------------------------------------------------------------------- 1 | # Examples for the `polars_excel_writer` library. 2 | 3 | This directory contains working examples showing different features of the 4 | `polars_excel_writer` library. 5 | 6 | The `app_{name}.rs` examples are small complete programs showing a feature or 7 | collection of features. 8 | 9 | The `doc_{struct}_{function}.rs` examples are more specific examples from the 10 | documentation and generally show how an individual function works. 11 | 12 | * `doc_write_excel_add_worksheet.rs` - An example of writing a Polar Rust 13 | dataframes to separate worksheets in an Excel workbook. 14 | 15 | * `doc_write_excel_autofit.rs` - An example of writing a Polar Rust 16 | dataframe to an Excel file. This example demonstrates autofitting column 17 | widths in the output worksheet. 18 | 19 | * `doc_write_excel_chart.rs` - An example of using `polars_excel_writer` in 20 | conjunction with `rust_xlsxwriter` to write a Polars dataframe to a 21 | worksheet and then add a chart to plot the data. 22 | 23 | * `doc_write_excel_combined.rs` - An example of writing a Polar Rust 24 | dataframe to an Excel file. 25 | 26 | * `doc_write_excel_date_format.rs` - An example of writing a Polar Rust 27 | dataframe to an Excel file. This example demonstrates how to change the 28 | default format for Polars date types. 29 | 30 | * `doc_write_excel_datetime_format.rs` - An example of writing a Polar Rust 31 | dataframe to an Excel file. This example demonstrates how to change the 32 | default format for Polars datetime types. 33 | 34 | * `doc_write_excel_float_format.rs` - An example of writing a Polar Rust 35 | dataframe to an Excel file. This demonstrates setting an Excel number 36 | format for floats. 37 | 38 | * `doc_write_excel_float_precision.rs` - An example of writing a Polar Rust 39 | dataframe to an Excel file. This example demonstrates how to set the 40 | precision of the float output. Setting the precision to 3 is equivalent 41 | to an Excel number format of `0.000`. 42 | 43 | * `doc_write_excel_intro.rs` - An example of writing a Polar Rust dataframe 44 | to an Excel file. 45 | 46 | * `doc_write_excel_null_values.rs` - An example of writing a Polar Rust 47 | dataframe to an Excel file. This demonstrates setting a value for Null 48 | values in the dataframe. The default is to write them as blank cells. 49 | 50 | * `doc_write_excel_set_column_format.rs` - An example of writing a Polar 51 | Rust dataframe to an Excel file. This demonstrates setting formats for 52 | different columns. 53 | 54 | * `doc_write_excel_set_freeze_panes_top_cell.rs` - An example of writing a 55 | Polar Rust dataframe to an Excel file. This demonstrates freezing the top 56 | row and setting a non-default first row within the pane. 57 | 58 | * `doc_write_excel_set_freeze_panes.rs` - An example of writing a Polar 59 | Rust dataframe to an Excel file. This demonstrates freezing the top row. 60 | 61 | * `doc_write_excel_set_header_format.rs` - An example of writing a Polar 62 | Rust dataframe to an Excel file. This demonstrates setting the format for 63 | the header row. 64 | 65 | * `doc_write_excel_set_header.rs` - An example of writing a Polar Rust 66 | dataframe to an Excel file. This demonstrates saving the dataframe 67 | without a header. 68 | 69 | * `doc_write_excel_set_nan_value.rs` - An example of writing a Polar Rust 70 | dataframe to an Excel file. This demonstrates handling NaN and Infinity 71 | values with custom string representations. 72 | 73 | * `doc_write_excel_set_screen_gridlines.rs` - An example of writing a Polar 74 | Rust dataframe to an Excel file. This demonstrates turning off the screen 75 | gridlines. 76 | 77 | * `doc_write_excel_set_table.rs` - An example of writing a Polar Rust 78 | dataframe to an Excel file. This demonstrates setting properties of the 79 | worksheet table that wraps the output dataframe. 80 | 81 | * `doc_write_excel_set_worksheet_name.rs` - An example of writing a Polar 82 | Rust dataframe to an Excel file. This demonstrates setting the name for 83 | the output worksheet. 84 | 85 | * `doc_write_excel_set_zoom.rs` - An example of writing a Polar Rust 86 | dataframe to an Excel file. This demonstrates setting the worksheet zoom 87 | level. 88 | 89 | * `doc_write_excel_time_format.rs` - An example of writing a Polar Rust 90 | dataframe to an Excel file. This example demonstrates how to change the 91 | default format for Polars time types. 92 | 93 | * `doc_write_excel_worksheet.rs` - An example of writing a Polar Rust 94 | dataframe to an Excel file. This demonstrates getting a reference to the 95 | worksheet used to write the dataframe and setting its tab color. 96 | 97 | * `doc_write_excel_write_dataframe_to_cell.rs` - An example of writing more 98 | than one Polar dataframes to an Excel worksheet. 99 | 100 | * `doc_write_excel_write_dataframe.rs` - An example of writing a Polar Rust 101 | dataframe to an Excel file. 102 | 103 | * `perf_test.rs` - Simple performance test to compare with the Python 104 | Polars example in `perf_test.py`. 105 | 106 | -------------------------------------------------------------------------------- /examples/doc_write_excel_add_worksheet.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframes to separate worksheets in an 6 | //! Excel workbook. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | let df1: DataFrame = df!( 14 | "Data 1" => &[10, 11, 12, 13, 14, 15], 15 | )?; 16 | 17 | let df2: DataFrame = df!( 18 | "Data 2" => &[20, 21, 22, 23, 24, 25], 19 | )?; 20 | 21 | // Create a new Excel writer. 22 | let mut excel_writer = PolarsExcelWriter::new(); 23 | 24 | // Write the first dataframe to the first/default worksheet. 25 | excel_writer.write_dataframe(&df1)?; 26 | 27 | // Add another worksheet and write the second dataframe to it. 28 | excel_writer.add_worksheet(); 29 | excel_writer.write_dataframe(&df2)?; 30 | 31 | // Save the file to disk. 32 | excel_writer.save("dataframe.xlsx")?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/doc_write_excel_autofit.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This example 6 | //! demonstrates autofitting column widths in the output worksheet. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "Col 1" => &["A", "B", "C", "D"], 16 | "Column 2" => &["A", "B", "C", "D"], 17 | "Column 3" => &["Hello", "World", "Hello, world", "Ciao"], 18 | "Column 4" => &[1234567, 12345678, 123456789, 1234567], 19 | )?; 20 | 21 | // Create a new Excel writer. 22 | let mut excel_writer = PolarsExcelWriter::new(); 23 | 24 | // Autofit the output data. 25 | excel_writer.set_autofit(true); 26 | 27 | // Write the dataframe to Excel. 28 | excel_writer.write_dataframe(&df)?; 29 | 30 | // Save the file to disk. 31 | excel_writer.save("dataframe.xlsx")?; 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/doc_write_excel_chart.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of using `polars_excel_writer` in conjunction with 6 | //! `rust_xlsxwriter` to write a Polars dataframe to a worksheet and then add a 7 | //! chart to plot the data. 8 | 9 | use polars::prelude::*; 10 | use polars_excel_writer::PolarsExcelWriter; 11 | use rust_xlsxwriter::{Chart, ChartType, Workbook}; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe using `Polars` 15 | let df: DataFrame = df!( 16 | "Data" => &[10, 20, 15, 25, 30, 20], 17 | )?; 18 | 19 | // Get some dataframe dimensions that we will use for the chart range. 20 | let row_min = 1; // Skip the header row. 21 | let row_max = df.height() as u32; 22 | 23 | // Create a new workbook and worksheet using `rust_xlsxwriter`. 24 | let mut workbook = Workbook::new(); 25 | let worksheet = workbook.add_worksheet(); 26 | 27 | // Write the dataframe to the worksheet using `PolarsExcelWriter`. 28 | let mut excel_writer = PolarsExcelWriter::new(); 29 | excel_writer.write_dataframe_to_worksheet(&df, worksheet, 0, 0)?; 30 | 31 | // Move back to `rust_xlsxwriter` to create a new chart and have it plot the 32 | // range of the dataframe in the worksheet. 33 | let mut chart = Chart::new(ChartType::Line); 34 | chart 35 | .add_series() 36 | .set_values(("Sheet1", row_min, 0, row_max, 0)); 37 | 38 | // Add the chart to the worksheet. 39 | worksheet.insert_chart(0, 2, &chart)?; 40 | 41 | // Save the file to disk. 42 | workbook.save("chart.xlsx")?; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /examples/doc_write_excel_combined.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. 6 | 7 | use chrono::prelude::*; 8 | use polars::prelude::*; 9 | 10 | fn main() { 11 | // Create a sample dataframe for the example. 12 | let df: DataFrame = df!( 13 | "String" => &["North", "South", "East", "West"], 14 | "Integer" => &[1, 2, 3, 4], 15 | "Float" => &[4.0, 5.0, 6.0, 7.0], 16 | "Time" => &[ 17 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 18 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 19 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 20 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 21 | ], 22 | "Date" => &[ 23 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(), 24 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap(), 25 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap(), 26 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap(), 27 | ], 28 | "Datetime" => &[ 29 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap(), 30 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap().and_hms_opt(2, 0, 0).unwrap(), 31 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap().and_hms_opt(3, 0, 0).unwrap(), 32 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap().and_hms_opt(4, 0, 0).unwrap(), 33 | ], 34 | ) 35 | .unwrap(); 36 | 37 | example(&df).unwrap(); 38 | } 39 | 40 | // The PolarsExcelWriter interface. 41 | use polars_excel_writer::PolarsExcelWriter; 42 | 43 | fn example(df: &DataFrame) -> PolarsResult<()> { 44 | let mut excel_writer = PolarsExcelWriter::new(); 45 | 46 | excel_writer.write_dataframe(df)?; 47 | excel_writer.save("dataframe.xlsx")?; 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/doc_write_excel_date_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This example 6 | //! demonstrates how to change the default format for Polars date types. 7 | 8 | use chrono::prelude::*; 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "Date" => &[ 17 | NaiveDate::from_ymd_opt(2023, 1, 11), 18 | NaiveDate::from_ymd_opt(2023, 1, 12), 19 | NaiveDate::from_ymd_opt(2023, 1, 13), 20 | NaiveDate::from_ymd_opt(2023, 1, 14), 21 | ], 22 | )?; 23 | 24 | // Create a new Excel writer. 25 | let mut excel_writer = PolarsExcelWriter::new(); 26 | 27 | // Set the date format. 28 | excel_writer.set_dtype_format(DataType::Date, "mmm d yyyy"); 29 | 30 | // Write the dataframe to Excel. 31 | excel_writer.write_dataframe(&df)?; 32 | 33 | // Save the file to disk. 34 | excel_writer.save("dataframe.xlsx")?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/doc_write_excel_datetime_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This example 6 | //! demonstrates how to change the default format for Polars datetime types. 7 | 8 | use chrono::prelude::*; 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "Datetime" => &[ 17 | NaiveDate::from_ymd_opt(2023, 1, 11).unwrap().and_hms_opt(1, 0, 0).unwrap(), 18 | NaiveDate::from_ymd_opt(2023, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(), 19 | NaiveDate::from_ymd_opt(2023, 1, 13).unwrap().and_hms_opt(3, 0, 0).unwrap(), 20 | NaiveDate::from_ymd_opt(2023, 1, 14).unwrap().and_hms_opt(4, 0, 0).unwrap(), 21 | ], 22 | )?; 23 | 24 | // Write the dataframe to an Excel file. 25 | let mut excel_writer = PolarsExcelWriter::new(); 26 | 27 | // Set the datetime format. 28 | excel_writer.set_dtype_datetime_format("hh::mm - mmm d yyyy"); 29 | 30 | // Write the dataframe to Excel. 31 | excel_writer.write_dataframe(&df)?; 32 | 33 | // Save the file to disk. 34 | excel_writer.save("dataframe.xlsx")?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/doc_write_excel_float_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting an Excel number format for floats. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "Float" => &[1000.0, 2000.22, 3000.333, 4000.4444], 16 | )?; 17 | 18 | // Write the dataframe to an Excel file. 19 | let mut excel_writer = PolarsExcelWriter::new(); 20 | 21 | // Set the float format. 22 | excel_writer.set_dtype_float_format("#,##0.00"); 23 | 24 | // Write the dataframe to Excel. 25 | excel_writer.write_dataframe(&df)?; 26 | 27 | // Save the file to disk. 28 | excel_writer.save("dataframe.xlsx")?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/doc_write_excel_float_precision.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This example 6 | //! demonstrates how to set the precision of the float output. Setting the 7 | //! precision to 3 is equivalent to an Excel number format of `0.000`. 8 | 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 17 | )?; 18 | 19 | // Write the dataframe to an Excel file. 20 | let mut excel_writer = PolarsExcelWriter::new(); 21 | 22 | // Set the float precision. 23 | excel_writer.set_float_precision(3); 24 | 25 | // Write the dataframe to Excel. 26 | excel_writer.write_dataframe(&df)?; 27 | 28 | // Save the file to disk. 29 | excel_writer.save("dataframe.xlsx")?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/doc_write_excel_intro.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. 6 | 7 | use chrono::prelude::*; 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Integer" => &[1, 2, 3, 4], 17 | "Float" => &[4.0, 5.0, 6.0, 7.0], 18 | "Time" => &[ 19 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 20 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 21 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 22 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 23 | ], 24 | "Date" => &[ 25 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(), 26 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap(), 27 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap(), 28 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap(), 29 | ], 30 | "Datetime" => &[ 31 | NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap(), 32 | NaiveDate::from_ymd_opt(2022, 1, 2).unwrap().and_hms_opt(2, 0, 0).unwrap(), 33 | NaiveDate::from_ymd_opt(2022, 1, 3).unwrap().and_hms_opt(3, 0, 0).unwrap(), 34 | NaiveDate::from_ymd_opt(2022, 1, 4).unwrap().and_hms_opt(4, 0, 0).unwrap(), 35 | ], 36 | )?; 37 | 38 | // Create a new Excel writer. 39 | let mut excel_writer = PolarsExcelWriter::new(); 40 | 41 | // Write the dataframe to Excel. 42 | excel_writer.write_dataframe(&df)?; 43 | 44 | // Save the file to disk. 45 | excel_writer.save("dataframe.xlsx")?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /examples/doc_write_excel_null_values.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting a value for Null values in the dataframe. The default 7 | //! is to write them as blank cells. 8 | 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a dataframe with Null values. 15 | let csv_string = "Foo,Bar\nNULL,B\nA,B\nA,NULL\nA,B\n"; 16 | let buffer = std::io::Cursor::new(csv_string); 17 | let df = CsvReadOptions::default() 18 | .map_parse_options(|parse_options| { 19 | parse_options.with_null_values(Some(NullValues::AllColumnsSingle("NULL".into()))) 20 | }) 21 | .into_reader_with_file_handle(buffer) 22 | .finish() 23 | .unwrap(); 24 | 25 | // Write the dataframe to an Excel file. 26 | let mut excel_writer = PolarsExcelWriter::new(); 27 | 28 | // Set an output string value for Null. 29 | excel_writer.set_null_value("Null"); 30 | 31 | // Write the dataframe to Excel. 32 | excel_writer.write_dataframe(&df)?; 33 | 34 | // Save the file to disk. 35 | excel_writer.save("dataframe.xlsx")?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_column_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting formats for different columns. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "East" => &[1.0, 2.22, 3.333, 4.4444], 16 | "West" => &[1.0, 2.22, 3.333, 4.4444], 17 | )?; 18 | 19 | // Write the dataframe to an Excel file. 20 | let mut excel_writer = PolarsExcelWriter::new(); 21 | 22 | // Set the number formats for the columns. 23 | excel_writer.set_column_format("East", "0.00"); 24 | excel_writer.set_column_format("West", "0.0000"); 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_freeze_panes.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates freezing the top row. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Int" => &[1, 2, 3, 4], 17 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 18 | )?; 19 | 20 | // Write the dataframe to an Excel file. 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | // Freeze the top row. 24 | excel_writer.set_freeze_panes(1, 0); 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_freeze_panes_top_cell.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates freezing the top row and setting a non-default first row within 7 | //! the pane. 8 | 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "String" => &["North", "South", "East", "West"], 17 | "Int" => &[1, 2, 3, 4], 18 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 19 | )?; 20 | 21 | // Write the dataframe to an Excel file. 22 | let mut excel_writer = PolarsExcelWriter::new(); 23 | 24 | // Freeze the top row and set the first row in the range. 25 | excel_writer.set_freeze_panes(1, 0); 26 | excel_writer.set_freeze_panes_top_cell(3, 0); 27 | 28 | // Write the dataframe to Excel. 29 | excel_writer.write_dataframe(&df)?; 30 | 31 | // Save the file to disk. 32 | excel_writer.save("dataframe.xlsx")?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_header.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates saving the dataframe without a header. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Int" => &[1, 2, 3, 4], 17 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 18 | )?; 19 | 20 | // Write the dataframe to an Excel file. 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | // Turn off the default header. 24 | excel_writer.set_header(false); 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_header_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting the format for the header row. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | use rust_xlsxwriter::Format; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "East" => &[1, 1, 1, 1], 17 | "West" => &[2, 2, 2, 2], 18 | "North" => &[3, 3, 3, 3], 19 | "South" => &[4, 4, 4, 4], 20 | )?; 21 | 22 | // Write the dataframe to an Excel file. 23 | let mut excel_writer = PolarsExcelWriter::new(); 24 | 25 | // Create an set the header format. 26 | let header_format = Format::new() 27 | .set_background_color("#C6EFCE") 28 | .set_font_color("#006100") 29 | .set_bold(); 30 | 31 | excel_writer.set_header_format(&header_format); 32 | 33 | // Write the dataframe to Excel. 34 | excel_writer.write_dataframe(&df)?; 35 | 36 | // Save the file to disk. 37 | excel_writer.save("dataframe.xlsx")?; 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_nan_value.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates handling NaN and Infinity values with custom string 7 | //! representations. 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "Default" => &["NAN", "INF", "-INF"], 16 | "Custom" => &[f64::NAN, f64::INFINITY, f64::NEG_INFINITY], 17 | )?; 18 | 19 | // Write the dataframe to an Excel file. 20 | let mut excel_writer = PolarsExcelWriter::new(); 21 | 22 | // Set custom values for NaN, Infinity, and -Infinity. 23 | excel_writer.set_nan_value("NaN"); 24 | excel_writer.set_infinity_value("Infinity"); 25 | excel_writer.set_neg_infinity_value("-Infinity"); 26 | 27 | // Autofit the output data, for clarity. 28 | excel_writer.set_autofit(true); 29 | 30 | // Write the dataframe to Excel. 31 | excel_writer.write_dataframe(&df)?; 32 | 33 | // Save the file to disk. 34 | excel_writer.save("dataframe.xlsx")?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_screen_gridlines.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates turning off the screen gridlines. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Int" => &[1, 2, 3, 4], 17 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 18 | )?; 19 | 20 | // Write the dataframe to an Excel file. 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | // Turn off the screen gridlines. 24 | excel_writer.set_screen_gridlines(false); 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_table.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting properties of the worksheet table that wraps the output 7 | //! dataframe. 8 | 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Table, TableStyle}; 13 | 14 | fn main() -> PolarsResult<()> { 15 | // Create a sample dataframe for the example. 16 | let df: DataFrame = df!( 17 | "String" => &["North", "South", "East", "West"], 18 | "Int" => &[1, 2, 3, 4], 19 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 20 | )?; 21 | 22 | // Write the dataframe to an Excel file. 23 | let mut excel_writer = PolarsExcelWriter::new(); 24 | 25 | // Add a `rust_xlsxwriter` table and set the style. 26 | let table = Table::new().set_style(TableStyle::Medium4); 27 | 28 | // Add the table to the Excel writer. 29 | excel_writer.set_table(&table); 30 | 31 | // Write the dataframe to Excel. 32 | excel_writer.write_dataframe(&df)?; 33 | 34 | // Save the file to disk. 35 | excel_writer.save("dataframe.xlsx")?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_worksheet_name.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting the name for the output worksheet. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Int" => &[1, 2, 3, 4], 17 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 18 | )?; 19 | 20 | // Write the dataframe to an Excel file. 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | // Set the worksheet name. 24 | excel_writer.set_worksheet_name("Polars Data")?; 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_set_zoom.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates setting the worksheet zoom level. 7 | 8 | use polars::prelude::*; 9 | 10 | use polars_excel_writer::PolarsExcelWriter; 11 | 12 | fn main() -> PolarsResult<()> { 13 | // Create a sample dataframe for the example. 14 | let df: DataFrame = df!( 15 | "String" => &["North", "South", "East", "West"], 16 | "Int" => &[1, 2, 3, 4], 17 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 18 | )?; 19 | 20 | // Write the dataframe to an Excel file. 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | // Set the worksheet zoom level. 24 | excel_writer.set_zoom(200); 25 | 26 | // Write the dataframe to Excel. 27 | excel_writer.write_dataframe(&df)?; 28 | 29 | // Save the file to disk. 30 | excel_writer.save("dataframe.xlsx")?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/doc_write_excel_time_format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This example 6 | //! demonstrates how to change the default format for Polars time types. 7 | 8 | use chrono::prelude::*; 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "Time" => &[ 17 | NaiveTime::from_hms_milli_opt(2, 00, 3, 456).unwrap(), 18 | NaiveTime::from_hms_milli_opt(2, 18, 3, 456).unwrap(), 19 | NaiveTime::from_hms_milli_opt(2, 37, 3, 456).unwrap(), 20 | NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 21 | ], 22 | )?; 23 | 24 | // Write the dataframe to an Excel file. 25 | let mut excel_writer = PolarsExcelWriter::new(); 26 | 27 | // Set the time format. 28 | excel_writer.set_dtype_format(DataType::Time, "hh:mm"); 29 | 30 | // Write the dataframe to Excel. 31 | excel_writer.write_dataframe(&df)?; 32 | 33 | // Save the file to disk. 34 | excel_writer.save("dataframe.xlsx")?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/doc_write_excel_worksheet.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. This 6 | //! demonstrates getting a reference to the worksheet used to write the 7 | //! dataframe and setting its tab color. 8 | 9 | use polars::prelude::*; 10 | 11 | use polars_excel_writer::PolarsExcelWriter; 12 | 13 | fn main() -> PolarsResult<()> { 14 | // Create a sample dataframe for the example. 15 | let df: DataFrame = df!( 16 | "String" => &["North", "South", "East", "West"], 17 | "Int" => &[1, 2, 3, 4], 18 | "Float" => &[1.0, 2.22, 3.333, 4.4444], 19 | )?; 20 | 21 | // Write the dataframe to an Excel file. 22 | let mut excel_writer = PolarsExcelWriter::new(); 23 | 24 | // Get the worksheet that the dataframe will be written to. 25 | let worksheet = excel_writer.worksheet()?; 26 | 27 | // Set the tab color for the worksheet using a `rust_xlsxwriter` worksheet 28 | // method. 29 | worksheet.set_tab_color("#FF9900"); 30 | 31 | // Write the dataframe to Excel. 32 | excel_writer.write_dataframe(&df)?; 33 | 34 | // Save the file to disk. 35 | excel_writer.save("dataframe.xlsx")?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/doc_write_excel_write_dataframe.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing a Polar Rust dataframe to an Excel file. 6 | 7 | use polars::prelude::*; 8 | 9 | use polars_excel_writer::PolarsExcelWriter; 10 | 11 | fn main() -> PolarsResult<()> { 12 | // Create a sample dataframe for the example. 13 | let df: DataFrame = df!( 14 | "Data" => &[10, 20, 15, 25, 30, 20], 15 | )?; 16 | 17 | // Write the dataframe to an Excel file. 18 | let mut excel_writer = PolarsExcelWriter::new(); 19 | 20 | excel_writer.write_dataframe(&df)?; 21 | 22 | // Save the file to disk. 23 | excel_writer.save("dataframe.xlsx")?; 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /examples/doc_write_excel_write_dataframe_to_cell.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! An example of writing more than one Polar dataframes to an Excel worksheet. 6 | 7 | use polars::prelude::*; 8 | 9 | use polars_excel_writer::PolarsExcelWriter; 10 | 11 | fn main() -> PolarsResult<()> { 12 | // Create a sample dataframe for the example. 13 | let df1: DataFrame = df!( 14 | "Data 1" => &[10, 20, 15, 25, 30, 20], 15 | )?; 16 | 17 | let df2: DataFrame = df!( 18 | "Data 2" => &[1.23, 2.34, 3.56], 19 | )?; 20 | 21 | // Write the dataframe to an Excel file. 22 | let mut excel_writer = PolarsExcelWriter::new(); 23 | 24 | // Write two dataframes to the same worksheet. 25 | excel_writer.write_dataframe_to_cell(&df1, 0, 0)?; 26 | excel_writer.write_dataframe_to_cell(&df2, 0, 2)?; 27 | 28 | // Save the file to disk. 29 | excel_writer.save("dataframe.xlsx")?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/perf_test.py: -------------------------------------------------------------------------------- 1 | # Sample Python Polars code to compare with the `perf_test.rs` example. The 2 | # following is based on code from Alexander Beedie in the following GitHub 3 | # thread: https://github.com/pola-rs/polars/issues/5568#issuecomment-1526316286 4 | 5 | from codetiming import Timer 6 | from datetime import date 7 | import polars as pl 8 | 9 | # Quickly spin-up a 1,000,000 element DataFrame. 10 | df = pl.DataFrame( 11 | { 12 | "Int": range(250_000), 13 | "Float": 123.456789, 14 | "Date": date.today(), 15 | "String": "Test" 16 | } 17 | ) 18 | 19 | # Export to Excel from polars. 20 | with Timer(): 21 | df.write_excel("dataframe_pl.xlsx") 22 | 23 | # Export to Excel from pandas. 24 | pf = df.to_pandas() 25 | with Timer(): 26 | pf.to_excel("dataframe_pd.xlsx", index=False) 27 | -------------------------------------------------------------------------------- /examples/perf_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 4 | 5 | //! Simple performance test to compare with the Python Polars example in 6 | //! `perf_test.py`. 7 | 8 | use std::time::Instant; 9 | 10 | use chrono::prelude::*; 11 | use polars::prelude::*; 12 | use polars_excel_writer::PolarsExcelWriter; 13 | 14 | const DATA_SIZE: usize = 250_000; 15 | 16 | fn main() { 17 | // Create a sample dataframe for testing. 18 | let df: DataFrame = df!( 19 | "Int" => &[1; DATA_SIZE], 20 | "Float" => &[123.456789; DATA_SIZE], 21 | "Date" => &[Utc::now().date_naive(); DATA_SIZE], 22 | "String" => &["Test"; DATA_SIZE], 23 | ) 24 | .unwrap(); 25 | 26 | let timer = Instant::now(); 27 | example(&df).unwrap(); 28 | println!("Elapsed time: {:.2?}", timer.elapsed()); 29 | } 30 | 31 | fn example(df: &DataFrame) -> PolarsResult<()> { 32 | let mut excel_writer = PolarsExcelWriter::new(); 33 | excel_writer.write_dataframe(df)?; 34 | excel_writer.save("dataframe_rs.xlsx")?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /src/changelog.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../CHANGELOG.md")] 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Entry point for `polars_excel_writer` library. 2 | // 3 | // SPDX-License-Identifier: MIT OR Apache-2.0 4 | // 5 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 6 | 7 | //! A crate for serializing Polars dataframes to Excel Xlsx files. 8 | //! 9 | //! The `polars_excel_writer` crate provides a primary interface 10 | //! [`PolarsExcelWriter`] which is a configurable Excel serializer that resembles 11 | //! the interface options provided by the Polars [`write_excel()`] dataframe 12 | //! method. 13 | //! 14 | //! This crate uses [`rust_xlsxwriter`] to do the Excel serialization and is 15 | //! typically 5x faster than Polars when exporting large dataframes to Excel, 16 | //! see the Performance data below. 17 | //! 18 | //![`write_excel()`]: 19 | //! https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.DataFrame.write_excel.html#polars.DataFrame.write_excel 20 | //! 21 | //! # Examples 22 | //! 23 | //! An example of writing a Polar Rust dataframe to an Excel file:. 24 | //! 25 | //! ```rust 26 | //! # // This code is available in examples/doc_write_excel_intro.rs 27 | //! # 28 | //! # use chrono::prelude::*; 29 | //! # use polars::prelude::*; 30 | //! # 31 | //! use polars_excel_writer::PolarsExcelWriter; 32 | //! 33 | //! fn main() -> PolarsResult<()> { 34 | //! // Create a sample dataframe for the example. 35 | //! let df: DataFrame = df!( 36 | //! "String" => &["North", "South", "East", "West"], 37 | //! "Integer" => &[1, 2, 3, 4], 38 | //! "Float" => &[4.0, 5.0, 6.0, 7.0], 39 | //! "Time" => &[ 40 | //! NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 41 | //! NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 42 | //! NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 43 | //! NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap(), 44 | //! ], 45 | //! "Date" => &[ 46 | //! NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(), 47 | //! NaiveDate::from_ymd_opt(2022, 1, 2).unwrap(), 48 | //! NaiveDate::from_ymd_opt(2022, 1, 3).unwrap(), 49 | //! NaiveDate::from_ymd_opt(2022, 1, 4).unwrap(), 50 | //! ], 51 | //! "Datetime" => &[ 52 | //! NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap(), 53 | //! NaiveDate::from_ymd_opt(2022, 1, 2).unwrap().and_hms_opt(2, 0, 0).unwrap(), 54 | //! NaiveDate::from_ymd_opt(2022, 1, 3).unwrap().and_hms_opt(3, 0, 0).unwrap(), 55 | //! NaiveDate::from_ymd_opt(2022, 1, 4).unwrap().and_hms_opt(4, 0, 0).unwrap(), 56 | //! ], 57 | //! ) 58 | //! .unwrap(); 59 | //! 60 | //! // Create a new Excel writer. 61 | //! let mut excel_writer = PolarsExcelWriter::new(); 62 | //! 63 | //! // Write the dataframe to Excel. 64 | //! excel_writer.write_dataframe(&df)?; 65 | //! 66 | //! // Save the file to disk. 67 | //! excel_writer.save("dataframe.xlsx")?; 68 | //! 69 | //! Ok(()) 70 | //! } 71 | //! ``` 72 | //! 73 | //! Output file: 74 | //! 75 | //! 76 | //! 77 | //! 78 | //! ## Performance 79 | //! 80 | //! The table below shows the performance of writing a dataframe using Python 81 | //! Polars, Python Pandas and `PolarsExcelWriter`. 82 | //! 83 | //! - Performance data: 84 | //! 85 | //! | Test Case | Time (s) | Relative (%) | 86 | //! | :---------------------------- | :------- | :----------- | 87 | //! | `Polars` | 6.49 | 100% | 88 | //! | `Pandas` | 10.92 | 168% | 89 | //! | `polars_excel_writer` | 1.22 | 19% | 90 | //! | `polars_excel_writer` + `zlib`| 1.08 | 17% | 91 | //! 92 | //! The tested configurations were: 93 | //! 94 | //! - `Polars`: The dataframe was created in Python Polars and written using the 95 | //! [`write_excel()`] function. See [`perf_test.py`]. 96 | //! - `Pandas`: The dataframe was created in Polars but converted to Pandas and 97 | //! then written via the Pandas [`to_excel()`] function. See also 98 | //! [`perf_test.py`]. 99 | //! - `polars_excel_writer`: The dataframe was created in Rust Polars and 100 | //! written using the `PolarsExcelWriter` interface. See [`perf_test.rs`]. 101 | //! - `polars_excel_writer` + `zlib`: Same as the previous test case but uses 102 | //! the `zlib` feature flag to enable the C zlib library in conjunction with 103 | //! the backend `ZipWriter`. 104 | //! 105 | //! **Note**: The performance was tested for the dataframe writing code only. 106 | //! The code used to create the dataframes was omitted from the test results. 107 | //! 108 | //! [`perf_test.py`]: 109 | //! https://github.com/jmcnamara/polars_excel_writer/blob/main/examples/perf_test.py 110 | //! [`perf_test.rs`]: 111 | //! https://github.com/jmcnamara/polars_excel_writer/blob/main/examples/perf_test.rs 112 | //! [`to_excel()`]: 113 | //! https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_excel.html 114 | //! 115 | //! 116 | //! 117 | //! ## See also 118 | //! 119 | //! - [`Changelog`](crate::changelog): Release notes and changelog. 120 | //! 121 | 122 | /// A module that exports the `PolarsExcelWriter` struct which provides the 123 | /// primary Excel Xlsx serializer that works with Polars dataframes and which 124 | /// can also interact with the [`rust_xlsxwriter`] writing engine that it wraps. 125 | /// 126 | pub mod excel_writer; 127 | 128 | #[doc(hidden)] 129 | pub use excel_writer::*; 130 | 131 | pub use PolarsExcelWriter; 132 | 133 | pub mod changelog; 134 | -------------------------------------------------------------------------------- /tests/input/dataframe01.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe01.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe02.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe02.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe03.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe03.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe04.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe04.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe05.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe05.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe06.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe06.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe07.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe07.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe08.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe08.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe09.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe09.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe10.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe10.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe11.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe11.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe12.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe12.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe13.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe13.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe14.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe14.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe15.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe15.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe16.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe16.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe17.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe17.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe18.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe18.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe19.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe19.xlsx -------------------------------------------------------------------------------- /tests/input/dataframe20.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmcnamara/polars_excel_writer/c35aca0fbdd93fa68dd0ebcaa5cb4293c1c0d97c/tests/input/dataframe20.xlsx -------------------------------------------------------------------------------- /tests/integration/common/mod.rs: -------------------------------------------------------------------------------- 1 | // Test helper functions for integration tests. These functions convert Excel 2 | // xml files into vectors of xml elements to make comparison testing easier. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | #[macro_export] 9 | macro_rules! assert_result { 10 | ( $x:expr_2021 ) => { 11 | match $x { 12 | Ok(result) => result, 13 | Err(e) => panic!("\n!\n! XlsxError:\n! {:?}\n!\n", e), 14 | } 15 | }; 16 | } 17 | 18 | macro_rules! static_regex { 19 | ($re:literal) => {{ 20 | static RE: std::sync::OnceLock = std::sync::OnceLock::new(); 21 | RE.get_or_init(|| regex::Regex::new($re).unwrap()) 22 | }}; 23 | } 24 | 25 | #[cfg(test)] 26 | use std::collections::hash_map::DefaultHasher; 27 | use std::collections::HashMap; 28 | use std::collections::HashSet; 29 | use std::fs; 30 | use std::fs::File; 31 | use std::hash::{Hash, Hasher}; 32 | use std::io::Read; 33 | 34 | use pretty_assertions::assert_eq; 35 | use regex::Regex; 36 | use rust_xlsxwriter::XlsxError; 37 | 38 | // Simple test runner struct and methods to create a new xlsx output file and 39 | // compare it with an input xlsx file created by Excel. 40 | #[allow(dead_code)] 41 | pub struct TestRunner<'a, F> 42 | where 43 | F: FnOnce(&str) -> Result<(), XlsxError> + Copy, 44 | { 45 | test_name: &'a str, 46 | test_function: Option, 47 | unique: &'a str, 48 | has_macros: bool, 49 | ignore_spans: bool, 50 | input_filename: String, 51 | output_filename: String, 52 | ignore_files: HashSet<&'a str>, 53 | ignore_elements: HashMap<&'a str, &'a str>, 54 | } 55 | 56 | impl<'a, F> TestRunner<'a, F> 57 | where 58 | F: FnOnce(&str) -> Result<(), XlsxError> + Copy, 59 | { 60 | pub fn new() -> TestRunner<'a, F> { 61 | TestRunner { 62 | test_name: "", 63 | test_function: None, 64 | unique: "", 65 | has_macros: false, 66 | ignore_spans: false, 67 | input_filename: String::new(), 68 | output_filename: String::new(), 69 | ignore_files: HashSet::new(), 70 | ignore_elements: HashMap::new(), 71 | } 72 | } 73 | 74 | // Set the testcase name. 75 | pub fn set_name(mut self, testcase: &'a str) -> TestRunner<'a, F> { 76 | self.test_name = testcase; 77 | self 78 | } 79 | 80 | // Set the test function pointer. 81 | pub fn set_function(mut self, test_function: F) -> TestRunner<'a, F> { 82 | self.test_function = Some(test_function); 83 | self 84 | } 85 | 86 | // Set string to add to the default output filename to make it unique so 87 | // that the multiple tests can be run in parallel. 88 | #[allow(dead_code)] 89 | pub fn unique(mut self, unique_string: &'a str) -> TestRunner<'a, F> { 90 | self.unique = unique_string; 91 | self 92 | } 93 | 94 | // Ignore certain xml files within the test xlsx files. 95 | #[allow(dead_code)] 96 | pub fn ignore_file(mut self, filename: &'a str) -> TestRunner<'a, F> { 97 | self.ignore_files.insert(filename); 98 | self 99 | } 100 | 101 | // Ignore the files associated with the formula xl/calcChain.xml. 102 | #[allow(dead_code)] 103 | pub fn ignore_calc_chain(mut self) -> TestRunner<'a, F> { 104 | self.ignore_files.insert("xl/calcChain.xml"); 105 | self.ignore_files.insert("[Content_Types].xml"); 106 | self.ignore_files.insert("xl/_rels/workbook.xml.rels"); 107 | self 108 | } 109 | 110 | // Take different naming for xlsm files into account. 111 | #[allow(dead_code)] 112 | pub fn has_macros(mut self) -> TestRunner<'a, F> { 113 | self.has_macros = true; 114 | self 115 | } 116 | 117 | // Ignore certain elements with xml files. 118 | #[allow(dead_code)] 119 | pub fn ignore_elements(mut self, filename: &'a str, pattern: &'a str) -> TestRunner<'a, F> { 120 | self.ignore_elements.insert(filename, pattern); 121 | self 122 | } 123 | 124 | // Ignore row span attributes in sheet.xml files, in "constant memory" mode. 125 | #[allow(dead_code)] 126 | pub fn ignore_worksheet_spans(mut self) -> TestRunner<'a, F> { 127 | self.ignore_spans = true; 128 | self 129 | } 130 | 131 | // Initialize the in/out filenames once other properties have been set. 132 | pub fn initialize(mut self) -> TestRunner<'a, F> { 133 | let extension = if self.has_macros { ".xlsm" } else { ".xlsx" }; 134 | 135 | self.input_filename = format!("tests/input/{}{}", self.test_name, extension); 136 | 137 | if self.unique.is_empty() { 138 | self.output_filename = format!("tests/output/rs_{}{}", self.test_name, extension); 139 | } else { 140 | self.output_filename = format!( 141 | "tests/output/rs_{}_{}{}", 142 | self.test_name, self.unique, extension 143 | ); 144 | } 145 | 146 | self 147 | } 148 | 149 | // Run the test function, check its result, and then test if the input and 150 | // generated output file are equal. 151 | pub fn assert_eq(&self) { 152 | // Get the test function and run it to generate the output file. 153 | let testcode = (self.test_function).unwrap(); 154 | let result = (testcode)(&self.output_filename); 155 | 156 | // Check for any XlsxError errors from the test code. 157 | assert_result!(result); 158 | 159 | // If the function ran correctly then compare the input/reference file 160 | // with the output/generated file. 161 | let (exp, got) = compare_xlsx_files( 162 | &self.input_filename, 163 | &self.output_filename, 164 | &self.ignore_files, 165 | &self.ignore_elements, 166 | self.ignore_spans, 167 | ); 168 | 169 | assert_eq!(exp, got); 170 | } 171 | 172 | // Clean up any the temp output file. 173 | pub fn cleanup(&self) { 174 | fs::remove_file(&self.output_filename).unwrap(); 175 | } 176 | } 177 | 178 | // Unzip 2 xlsx files and compare whether they have the same filenames and 179 | // structure. If they are the same then we compare each xml file to ensure that 180 | // files created by rust_xlsxwriter are the same as test files created in Excel. 181 | // Returns two String vectors for comparison testing. 182 | fn compare_xlsx_files( 183 | exp_file: &str, 184 | got_file: &str, 185 | ignore_files: &HashSet<&str>, 186 | ignore_elements: &HashMap<&str, &str>, 187 | ignore_spans: bool, 188 | ) -> (Vec, Vec) { 189 | // Regexes used in the text cleaning. 190 | let spans = static_regex!(r#" spans="\d+:\d+""#); 191 | let digits = static_regex!(r"000000000000\d+"); 192 | let utc_date = static_regex!(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"); 193 | let calc_para = static_regex!(r"]*>"); 194 | let workbook_view = static_regex!( 195 | r#" fh, 201 | Err(err) => { 202 | return ( 203 | vec![exp_file.to_string(), err.to_string()], 204 | vec![got_file.to_string()], 205 | ) 206 | } 207 | }; 208 | let got_fh = match File::open(got_file) { 209 | Ok(fh) => fh, 210 | Err(err) => { 211 | return ( 212 | vec![exp_file.to_string()], 213 | vec![got_file.to_string(), err.to_string()], 214 | ) 215 | } 216 | }; 217 | 218 | // Open the zip structure that comprises an xlsx file. 219 | let mut exp_zip = match zip::ZipArchive::new(exp_fh) { 220 | Ok(fh) => fh, 221 | Err(err) => { 222 | return ( 223 | vec![exp_file.to_string(), err.to_string()], 224 | vec![got_file.to_string()], 225 | ) 226 | } 227 | }; 228 | let mut got_zip = match zip::ZipArchive::new(got_fh) { 229 | Ok(fh) => fh, 230 | Err(err) => { 231 | return ( 232 | vec![exp_file.to_string()], 233 | vec![got_file.to_string(), err.to_string()], 234 | ) 235 | } 236 | }; 237 | 238 | // Iterate through each xml file in the xlsx/zip container and read the 239 | // xml data as a string. 240 | let mut exp_filenames = vec![]; 241 | let mut got_filenames = vec![]; 242 | let mut exp_xml: HashMap = HashMap::new(); 243 | let mut got_xml: HashMap = HashMap::new(); 244 | 245 | for i in 0..exp_zip.len() { 246 | let mut file = match exp_zip.by_index(i) { 247 | Ok(file) => file, 248 | Err(err) => { 249 | return ( 250 | vec![exp_file.to_string(), err.to_string()], 251 | vec![got_file.to_string()], 252 | ) 253 | } 254 | }; 255 | 256 | // Ignore any test specific files like "xl/calcChain.xml". 257 | if ignore_files.contains(file.name()) { 258 | continue; 259 | } 260 | 261 | // Store the filenames for comparison of the file structure. 262 | exp_filenames.push(file.name().to_string()); 263 | 264 | if is_binary_file(file.name()) { 265 | // Get a checksum for binary files. 266 | let mut bin_data: Vec = vec![]; 267 | file.read_to_end(&mut bin_data).unwrap(); 268 | let mut hasher = DefaultHasher::new(); 269 | bin_data.hash(&mut hasher); 270 | let xml_data = format!("checksum = {}", hasher.finish()); 271 | exp_xml.insert(file.name().to_string(), xml_data); 272 | } else { 273 | // Read XML data from non-binary files. 274 | let mut xml_data = String::new(); 275 | file.read_to_string(&mut xml_data).unwrap(); 276 | exp_xml.insert(file.name().to_string(), xml_data); 277 | } 278 | } 279 | 280 | for i in 0..got_zip.len() { 281 | let mut file = match got_zip.by_index(i) { 282 | Ok(file) => file, 283 | Err(err) => { 284 | return ( 285 | vec![exp_file.to_string()], 286 | vec![got_file.to_string(), err.to_string()], 287 | ) 288 | } 289 | }; 290 | 291 | // Ignore any test specific files like "xl/calcChain.xml". 292 | if ignore_files.contains(file.name()) { 293 | continue; 294 | } 295 | 296 | // Store the filenames for comparison of the file structure. 297 | got_filenames.push(file.name().to_string()); 298 | 299 | if is_binary_file(file.name()) { 300 | // Get a checksum for binary files. 301 | let mut bin_data: Vec = vec![]; 302 | file.read_to_end(&mut bin_data).unwrap(); 303 | let mut hasher = DefaultHasher::new(); 304 | bin_data.hash(&mut hasher); 305 | let xml_data = format!("checksum = {}", hasher.finish()); 306 | got_xml.insert(file.name().to_string(), xml_data); 307 | } else { 308 | // Read XML data from non-binary files. 309 | let mut xml_data = String::new(); 310 | file.read_to_string(&mut xml_data).unwrap(); 311 | 312 | // Check for test generator XML error. 313 | if xml_data.contains("<<") { 314 | return ( 315 | vec![file.name().to_string()], 316 | vec![ 317 | "Generated XML contains double start tag".to_string(), 318 | String::new(), 319 | ], 320 | ); 321 | } 322 | 323 | got_xml.insert(file.name().to_string(), xml_data); 324 | } 325 | } 326 | 327 | // Sort the xlsx filenames/structure 328 | exp_filenames.sort(); 329 | got_filenames.sort(); 330 | 331 | if exp_filenames != got_filenames { 332 | return (exp_filenames, got_filenames); 333 | } 334 | 335 | for filename in exp_filenames { 336 | let mut exp_xml_string = exp_xml.get(&filename).unwrap().to_string(); 337 | let mut got_xml_string = got_xml.get(&filename).unwrap().to_string(); 338 | 339 | // Remove author name and creation date metadata from core.x¦ml file. 340 | if filename == "docProps/core.xml" { 341 | // Removed author name from test input files created in Excel. 342 | exp_xml_string = exp_xml_string.replace("John", ""); 343 | 344 | // Remove creation date from core.xml file. 345 | exp_xml_string = utc_date.replace_all(&exp_xml_string, "").to_string(); 346 | got_xml_string = utc_date.replace_all(&got_xml_string, "").to_string(); 347 | } 348 | 349 | // Remove workbookView dimensions which are almost always different and 350 | // calcPr which can have different Excel version ids. 351 | if filename == "xl/workbook.xml" { 352 | exp_xml_string = workbook_view 353 | .replace(&exp_xml_string, "").to_string(); 360 | got_xml_string = calc_para.replace(&got_xml_string, "").to_string(); 361 | } 362 | 363 | // The pageMargins element in chart files often contain values like 364 | // "0.75000000000000011" instead of "0.75". We simplify/round these to 365 | // make comparison easier. 366 | if filename.starts_with("xl/charts/chart") { 367 | exp_xml_string = digits.replace_all(&exp_xml_string, "").to_string(); 368 | } 369 | 370 | // Ignore/remove span elements for "constant mode" comparison. 371 | if ignore_spans && filename.starts_with("xl/worksheets/sheet") { 372 | exp_xml_string = spans.replace_all(&exp_xml_string, "").to_string(); 373 | got_xml_string = spans.replace_all(&got_xml_string, "").to_string(); 374 | } 375 | 376 | // Convert the xml strings to vectors for easier comparison. 377 | let mut exp_xml_vec; 378 | let mut got_xml_vec; 379 | if filename.ends_with(".vml") { 380 | exp_xml_vec = vml_to_vec(&exp_xml_string); 381 | got_xml_vec = vml_to_vec(&got_xml_string); 382 | } else { 383 | exp_xml_vec = xml_to_vec(&exp_xml_string); 384 | got_xml_vec = xml_to_vec(&got_xml_string); 385 | } 386 | 387 | // Reorder randomized XML elements in some xlsx xml files to 388 | // allow comparison testing. 389 | if filename == "[Content_Types].xml" || filename.ends_with(".rels") { 390 | exp_xml_vec = sort_xml_file_data(exp_xml_vec); 391 | got_xml_vec = sort_xml_file_data(got_xml_vec); 392 | } 393 | 394 | // Ignore certain elements within files, for example which 395 | // changes in the lower decimal places. 396 | if ignore_elements.contains_key(filename.as_str()) { 397 | let pattern = ignore_elements.get(filename.as_str()).unwrap(); 398 | let re = Regex::new(pattern).unwrap(); 399 | 400 | exp_xml_vec = exp_xml_vec 401 | .into_iter() 402 | .filter(|x| !re.is_match(x)) 403 | .collect::>(); 404 | 405 | got_xml_vec = got_xml_vec 406 | .into_iter() 407 | .filter(|x| !re.is_match(x)) 408 | .collect::>(); 409 | } 410 | 411 | // Indent XML elements to make the visual comparison of failures easier. 412 | exp_xml_vec = indent_elements(&exp_xml_vec); 413 | got_xml_vec = indent_elements(&got_xml_vec); 414 | 415 | // Add the filename to the xml vector to help identify where 416 | // differences occurs. 417 | exp_xml_vec.insert(0, filename.to_string()); 418 | got_xml_vec.insert(0, filename.to_string()); 419 | 420 | if exp_xml_vec != got_xml_vec { 421 | return (exp_xml_vec, got_xml_vec); 422 | } 423 | } 424 | 425 | (vec![String::from("Ok")], vec![String::from("Ok")]) 426 | } 427 | 428 | // Convert XML string/doc into a vector for comparison testing. 429 | fn xml_to_vec(xml_string: &str) -> Vec { 430 | let element_dividers = static_regex!(r">\s*<"); 431 | 432 | let mut xml_elements: Vec = Vec::new(); 433 | let tokens: Vec<&str> = element_dividers.split(xml_string).collect(); 434 | 435 | for token in &tokens { 436 | let mut element = token.trim().to_string(); 437 | element = element.replace('\r', ""); 438 | 439 | // Add back the removed brackets. 440 | if !element.starts_with('<') { 441 | element = format!("<{element}"); 442 | } 443 | if !element.ends_with('>') { 444 | element = format!("{element}>"); 445 | } 446 | 447 | xml_elements.push(element); 448 | } 449 | xml_elements 450 | } 451 | 452 | // Convert VML string/doc into a vector for comparison testing. Excel VML tends 453 | // to be less structured than other XML so it needs more massaging. 454 | pub(crate) fn vml_to_vec(vml_string: &str) -> Vec { 455 | let whitespace = static_regex!(r"\s+"); 456 | 457 | let mut vml_string = vml_string.replace(['\r', '\n'], ""); 458 | vml_string = whitespace.replace_all(&vml_string, " ").into(); 459 | 460 | vml_string = vml_string 461 | .replace("; ", ";") 462 | .replace('\'', "\"") 463 | .replace(" ", ""); 464 | 465 | xml_to_vec(&vml_string) 466 | } 467 | 468 | // Indent XML elements to make the visual comparison of failures easier. 469 | fn indent_elements(xml_elements: &Vec) -> Vec { 470 | let mut indented: Vec = Vec::new(); 471 | let mut indent_level = 0; 472 | 473 | for element in xml_elements { 474 | if element.starts_with("(); 479 | indented.push(format!("{indentation}{element}")); 480 | 481 | if !element.starts_with("") { 482 | indent_level += 1; 483 | } 484 | } 485 | 486 | indented 487 | } 488 | 489 | // Re-order the elements in an vec of XML elements for comparison purposes. This 490 | // is necessary since Excel can produce the elements of some files, for example 491 | // Content_Types and relationship/.rel files, in a semi-random/hash order. 492 | fn sort_xml_file_data(mut xml_elements: Vec) -> Vec { 493 | // We don't want to sort the start and end elements. 494 | let first = xml_elements.remove(0); 495 | let second = xml_elements.remove(0); 496 | let last = xml_elements.pop().unwrap(); 497 | 498 | // Sort the rest of the elements. 499 | xml_elements.sort(); 500 | 501 | // Add back the start and end elements. 502 | xml_elements.insert(0, second); 503 | xml_elements.insert(0, first); 504 | xml_elements.push(last); 505 | 506 | xml_elements 507 | } 508 | 509 | // Check for binary files (as opposed to XML files). 510 | fn is_binary_file(filename: &str) -> bool { 511 | filename.ends_with(".png") 512 | || filename.ends_with(".jpeg") 513 | || filename.ends_with(".bmp") 514 | || filename.ends_with(".gif") 515 | || filename.ends_with(".bin") 516 | } 517 | 518 | // Create the data structure used in the autofilter tests. 519 | #[allow(dead_code)] 520 | pub fn get_autofilter_data() -> Vec<(&'static str, &'static str, u16, &'static str)> { 521 | vec![ 522 | ("East", "Apple", 9000, "July"), 523 | ("East", "Apple", 5000, "July"), 524 | ("South", "Orange", 9000, "September"), 525 | ("North", "Apple", 2000, "November"), 526 | ("West", "Apple", 9000, "November"), 527 | ("South", "Pear", 7000, "October"), 528 | ("North", "Pear", 9000, "August"), 529 | ("West", "Orange", 1000, "December"), 530 | ("West", "Grape", 1000, "November"), 531 | ("South", "Pear", 10000, "April"), 532 | ("West", "Grape", 6000, "January"), 533 | ("South", "Orange", 3000, "May"), 534 | ("North", "Apple", 3000, "December"), 535 | ("South", "Apple", 7000, "February"), 536 | ("West", "Grape", 1000, "December"), 537 | ("East", "Grape", 8000, "February"), 538 | ("South", "Grape", 10000, "June"), 539 | ("West", "Pear", 7000, "December"), 540 | ("South", "Apple", 2000, "October"), 541 | ("East", "Grape", 7000, "December"), 542 | ("North", "Grape", 6000, "April"), 543 | ("East", "Pear", 8000, "February"), 544 | ("North", "Apple", 7000, "August"), 545 | ("North", "Orange", 7000, "July"), 546 | ("North", "Apple", 6000, "June"), 547 | ("South", "Grape", 8000, "September"), 548 | ("West", "Apple", 3000, "October"), 549 | ("South", "Orange", 10000, "November"), 550 | ("West", "Grape", 4000, "July"), 551 | ("North", "Orange", 5000, "August"), 552 | ("East", "Orange", 1000, "November"), 553 | ("East", "Orange", 4000, "October"), 554 | ("North", "Grape", 5000, "August"), 555 | ("East", "Apple", 1000, "December"), 556 | ("South", "Apple", 10000, "March"), 557 | ("East", "Grape", 7000, "October"), 558 | ("West", "Grape", 1000, "September"), 559 | ("East", "Grape", 10000, "October"), 560 | ("South", "Orange", 8000, "March"), 561 | ("North", "Apple", 4000, "July"), 562 | ("South", "Orange", 5000, "July"), 563 | ("West", "Apple", 4000, "June"), 564 | ("East", "Apple", 5000, "April"), 565 | ("North", "Pear", 3000, "August"), 566 | ("East", "Grape", 9000, "November"), 567 | ("North", "Orange", 8000, "October"), 568 | ("East", "Apple", 10000, "June"), 569 | ("South", "Pear", 1000, "December"), 570 | ("North", "Grape", 10000, "July"), 571 | ("East", "Grape", 6000, "February"), 572 | ] 573 | } 574 | -------------------------------------------------------------------------------- /tests/integration/dataframe01.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Workbook, XlsxError}; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.write_dataframe(&df)?; 23 | excel_writer.save(filename)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | // Compare output against target Excel file using rust_xlsxwriter. 29 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 30 | let df: DataFrame = df!( 31 | "Foo" => &[1, 1, 1], 32 | "Bar" => &[2, 2, 2], 33 | )?; 34 | 35 | let mut workbook = Workbook::new(); 36 | let worksheet = workbook.add_worksheet(); 37 | 38 | let mut excel_writer = PolarsExcelWriter::new(); 39 | 40 | excel_writer.write_dataframe_to_worksheet(&df, worksheet, 0, 0)?; 41 | 42 | workbook.save(filename)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | // Check CSV input which should default to u64. 48 | fn create_new_xlsx_file_3(filename: &str) -> Result<(), XlsxError> { 49 | let csv_string = "Foo,Bar\n1,2\n1,2\n1,2\n"; 50 | let buffer = std::io::Cursor::new(csv_string); 51 | let df = CsvReadOptions::default() 52 | .map_parse_options(|parse_options| { 53 | parse_options.with_null_values(Some(NullValues::AllColumnsSingle("NULL".into()))) 54 | }) 55 | .into_reader_with_file_handle(buffer) 56 | .finish()?; 57 | 58 | let mut workbook = Workbook::new(); 59 | let worksheet = workbook.add_worksheet(); 60 | 61 | let mut excel_writer = PolarsExcelWriter::new(); 62 | 63 | excel_writer.write_dataframe_to_worksheet(&df, worksheet, 0, 0)?; 64 | 65 | workbook.save(filename)?; 66 | 67 | Ok(()) 68 | } 69 | 70 | #[test] 71 | fn dataframe_write_excel01() { 72 | let test_runner = common::TestRunner::new() 73 | .set_name("dataframe01") 74 | .set_function(create_new_xlsx_file_1) 75 | .unique("2") 76 | .initialize(); 77 | 78 | test_runner.assert_eq(); 79 | test_runner.cleanup(); 80 | } 81 | 82 | #[test] 83 | fn dataframe_to_worksheet01() { 84 | let test_runner = common::TestRunner::new() 85 | .set_name("dataframe01") 86 | .set_function(create_new_xlsx_file_2) 87 | .unique("3") 88 | .initialize(); 89 | 90 | test_runner.assert_eq(); 91 | test_runner.cleanup(); 92 | } 93 | 94 | #[test] 95 | fn dataframe_from_csv01() { 96 | let test_runner = common::TestRunner::new() 97 | .set_name("dataframe01") 98 | .set_function(create_new_xlsx_file_3) 99 | .unique("4") 100 | .initialize(); 101 | 102 | test_runner.assert_eq(); 103 | test_runner.cleanup(); 104 | } 105 | -------------------------------------------------------------------------------- /tests/integration/dataframe02.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Table, XlsxError}; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Column1" => &["Foo", "Foo", "Foo"], 18 | "Column2" => &["Bar", "Bar", "Bar"], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_header(false); 23 | excel_writer.set_autofit(true); 24 | 25 | excel_writer.write_dataframe(&df)?; 26 | excel_writer.save(filename)?; 27 | 28 | Ok(()) 29 | } 30 | 31 | // Compare using PolarsExcelWriter and set_table(). 32 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 33 | let df: DataFrame = df!( 34 | "Column1" => &["Foo", "Foo", "Foo"], 35 | "Column2" => &["Bar", "Bar", "Bar"], 36 | )?; 37 | 38 | let mut excel_writer = PolarsExcelWriter::new(); 39 | let table = Table::new().set_header_row(false); 40 | 41 | excel_writer.set_table(&table); 42 | excel_writer.set_autofit(true); 43 | 44 | excel_writer.write_dataframe(&df)?; 45 | excel_writer.save(filename)?; 46 | 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn dataframe_write_excel02_1() { 52 | let test_runner = common::TestRunner::new() 53 | .set_name("dataframe02") 54 | .set_function(create_new_xlsx_file_1) 55 | .unique("2") 56 | .initialize(); 57 | 58 | test_runner.assert_eq(); 59 | test_runner.cleanup(); 60 | } 61 | 62 | #[test] 63 | fn dataframe_write_excel02_2() { 64 | let test_runner = common::TestRunner::new() 65 | .set_name("dataframe02") 66 | .set_function(create_new_xlsx_file_2) 67 | .unique("3") 68 | .initialize(); 69 | 70 | test_runner.assert_eq(); 71 | test_runner.cleanup(); 72 | } 73 | -------------------------------------------------------------------------------- /tests/integration/dataframe03.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.write_dataframe_to_cell(&df, 1, 1)?; 23 | excel_writer.save(filename)?; 24 | 25 | Ok(()) 26 | } 27 | 28 | #[test] 29 | fn dataframe_write_excel03() { 30 | let test_runner = common::TestRunner::new() 31 | .set_name("dataframe03") 32 | .set_function(create_new_xlsx_file) 33 | .initialize(); 34 | 35 | test_runner.assert_eq(); 36 | test_runner.cleanup(); 37 | } 38 | -------------------------------------------------------------------------------- /tests/integration/dataframe04.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | excel_writer.write_dataframe_to_cell(&df, 0, 0)?; 24 | excel_writer.write_dataframe_to_cell(&df, 0, 3)?; 25 | 26 | excel_writer.save(filename)?; 27 | 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | fn dataframe_write_excel04() { 33 | let test_runner = common::TestRunner::new() 34 | .set_name("dataframe04") 35 | .set_function(create_new_xlsx_file) 36 | .initialize(); 37 | 38 | test_runner.assert_eq(); 39 | test_runner.cleanup(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/dataframe05.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Table, TableColumn, TableFunction, TableStyle, XlsxError}; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[2, -1, -1], 18 | "Bar" => &[2, 2, -4], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | let columns = vec![ 24 | TableColumn::new().set_total_function(TableFunction::Sum), 25 | TableColumn::new().set_total_function(TableFunction::Sum), 26 | ]; 27 | 28 | let table = Table::new() 29 | .set_style(TableStyle::None) 30 | .set_first_column(true) 31 | .set_last_column(true) 32 | .set_total_row(true) 33 | .set_columns(&columns); 34 | 35 | excel_writer.set_table(&table); 36 | excel_writer.write_dataframe(&df)?; 37 | 38 | excel_writer.save(filename)?; 39 | 40 | Ok(()) 41 | } 42 | 43 | #[test] 44 | fn dataframe_write_excel05() { 45 | let test_runner = common::TestRunner::new() 46 | .set_name("dataframe05") 47 | .set_function(create_new_xlsx_file) 48 | .ignore_calc_chain() 49 | .initialize(); 50 | 51 | test_runner.assert_eq(); 52 | test_runner.cleanup(); 53 | } 54 | -------------------------------------------------------------------------------- /tests/integration/dataframe06.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_worksheet_name("Data")?; 23 | excel_writer.write_dataframe(&df)?; 24 | excel_writer.save(filename)?; 25 | 26 | Ok(()) 27 | } 28 | 29 | // Get the current worksheet and set the name that way. 30 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 31 | let df: DataFrame = df!( 32 | "Foo" => &[1, 1, 1], 33 | "Bar" => &[2, 2, 2], 34 | )?; 35 | 36 | let mut excel_writer = PolarsExcelWriter::new(); 37 | 38 | let worksheet = excel_writer.worksheet()?; 39 | worksheet.set_name("Data")?; 40 | 41 | excel_writer.write_dataframe(&df)?; 42 | excel_writer.save(filename)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | fn dataframe_write_excel06_1() { 49 | let test_runner = common::TestRunner::new() 50 | .set_name("dataframe06") 51 | .set_function(create_new_xlsx_file_1) 52 | .unique("1") 53 | .initialize(); 54 | 55 | test_runner.assert_eq(); 56 | test_runner.cleanup(); 57 | } 58 | 59 | #[test] 60 | fn dataframe_write_excel06_2() { 61 | let test_runner = common::TestRunner::new() 62 | .set_name("dataframe06") 63 | .set_function(create_new_xlsx_file_2) 64 | .unique("2") 65 | .initialize(); 66 | 67 | test_runner.assert_eq(); 68 | test_runner.cleanup(); 69 | } 70 | -------------------------------------------------------------------------------- /tests/integration/dataframe07.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | 23 | excel_writer.write_dataframe(&df)?; 24 | 25 | excel_writer.add_worksheet(); 26 | excel_writer.write_dataframe(&df)?; 27 | 28 | excel_writer.save(filename)?; 29 | 30 | Ok(()) 31 | } 32 | 33 | #[test] 34 | fn dataframe_write_excel07() { 35 | let test_runner = common::TestRunner::new() 36 | .set_name("dataframe07") 37 | .set_function(create_new_xlsx_file) 38 | .ignore_calc_chain() 39 | .initialize(); 40 | 41 | test_runner.assert_eq(); 42 | test_runner.cleanup(); 43 | } 44 | -------------------------------------------------------------------------------- /tests/integration/dataframe08.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_zoom(200); 23 | 24 | excel_writer.write_dataframe(&df)?; 25 | excel_writer.save(filename)?; 26 | 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn dataframe_write_excel08() { 32 | let test_runner = common::TestRunner::new() 33 | .set_name("dataframe08") 34 | .set_function(create_new_xlsx_file) 35 | .ignore_calc_chain() 36 | .initialize(); 37 | 38 | test_runner.assert_eq(); 39 | test_runner.cleanup(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/dataframe09.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_screen_gridlines(false); 23 | 24 | excel_writer.write_dataframe(&df)?; 25 | excel_writer.save(filename)?; 26 | 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn dataframe_write_excel09() { 32 | let test_runner = common::TestRunner::new() 33 | .set_name("dataframe09") 34 | .set_function(create_new_xlsx_file) 35 | .ignore_calc_chain() 36 | .initialize(); 37 | 38 | test_runner.assert_eq(); 39 | test_runner.cleanup(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/dataframe10.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_freeze_panes(1, 0); 23 | 24 | excel_writer.write_dataframe(&df)?; 25 | excel_writer.save(filename)?; 26 | 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn dataframe_write_excel10() { 32 | let test_runner = common::TestRunner::new() 33 | .set_name("dataframe10") 34 | .set_function(create_new_xlsx_file) 35 | .ignore_calc_chain() 36 | .initialize(); 37 | 38 | test_runner.assert_eq(); 39 | test_runner.cleanup(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/dataframe11.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_freeze_panes(1, 0); 23 | excel_writer.set_freeze_panes_top_cell(2, 0); 24 | 25 | excel_writer.write_dataframe(&df)?; 26 | excel_writer.save(filename)?; 27 | 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | fn dataframe_write_excel11() { 33 | let test_runner = common::TestRunner::new() 34 | .set_name("dataframe11") 35 | .set_function(create_new_xlsx_file) 36 | .ignore_calc_chain() 37 | .initialize(); 38 | 39 | test_runner.assert_eq(); 40 | test_runner.cleanup(); 41 | } 42 | -------------------------------------------------------------------------------- /tests/integration/dataframe12.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | excel_writer.set_autofilter(false); 23 | 24 | excel_writer.write_dataframe(&df)?; 25 | excel_writer.save(filename)?; 26 | 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn dataframe_write_excel12() { 32 | let test_runner = common::TestRunner::new() 33 | .set_name("dataframe12") 34 | .set_function(create_new_xlsx_file) 35 | .ignore_calc_chain() 36 | .initialize(); 37 | 38 | test_runner.assert_eq(); 39 | test_runner.cleanup(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/dataframe13.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Format, XlsxError}; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. This file 15 | // has bold integers in the first column. 16 | 17 | // Use dtype formatting. 18 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 19 | let df: DataFrame = df!( 20 | "Foo" => &[1, 1, 1], 21 | "Bar" => &[2.4, 2.4, 2.4], 22 | )?; 23 | 24 | let mut excel_writer = PolarsExcelWriter::new(); 25 | let format = Format::new().set_bold(); 26 | 27 | excel_writer.set_dtype_format(DataType::Int32, format); 28 | 29 | excel_writer.write_dataframe(&df)?; 30 | excel_writer.save(filename)?; 31 | 32 | Ok(()) 33 | } 34 | 35 | // Ensure column formatting overrides dtype formatting. 36 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 37 | let df: DataFrame = df!( 38 | "Foo" => &[1, 1, 1], 39 | "Bar" => &[2.4, 2.4, 2.4], 40 | )?; 41 | 42 | let mut excel_writer = PolarsExcelWriter::new(); 43 | let format = Format::new().set_bold(); 44 | 45 | excel_writer.set_dtype_format(DataType::Int32, &format); 46 | excel_writer.set_column_format("Foo", &format); 47 | 48 | excel_writer.write_dataframe(&df)?; 49 | excel_writer.save(filename)?; 50 | 51 | Ok(()) 52 | } 53 | 54 | // Use column formatting. 55 | fn create_new_xlsx_file_3(filename: &str) -> Result<(), XlsxError> { 56 | let df: DataFrame = df!( 57 | "Foo" => &[1, 1, 1], 58 | "Bar" => &[2.4, 2.4, 2.4], 59 | )?; 60 | 61 | let mut excel_writer = PolarsExcelWriter::new(); 62 | let format = Format::new().set_bold(); 63 | 64 | excel_writer.set_column_format("Foo", format); 65 | 66 | excel_writer.write_dataframe(&df)?; 67 | excel_writer.save(filename)?; 68 | 69 | Ok(()) 70 | } 71 | 72 | // Use all int dtype formatting. 73 | fn create_new_xlsx_file_4(filename: &str) -> Result<(), XlsxError> { 74 | let df: DataFrame = df!( 75 | "Foo" => &[1, 1, 1], 76 | "Bar" => &[2.4, 2.4, 2.4], 77 | )?; 78 | 79 | let mut excel_writer = PolarsExcelWriter::new(); 80 | let format = Format::new().set_bold(); 81 | 82 | excel_writer.set_dtype_int_format(format); 83 | 84 | excel_writer.write_dataframe(&df)?; 85 | excel_writer.save(filename)?; 86 | 87 | Ok(()) 88 | } 89 | 90 | // Use all number dtype formatting. 91 | fn create_new_xlsx_file_5(filename: &str) -> Result<(), XlsxError> { 92 | let df: DataFrame = df!( 93 | "Foo" => &[1, 1, 1], 94 | "Bar" => &[2.4, 2.4, 2.4], 95 | )?; 96 | 97 | let mut excel_writer = PolarsExcelWriter::new(); 98 | let num_format = Format::new().set_bold(); 99 | let default_format = Format::new(); 100 | 101 | excel_writer.set_dtype_number_format(num_format); 102 | 103 | // Override the number format for the "Bar" column. 104 | excel_writer.set_column_format("Bar", default_format); 105 | 106 | excel_writer.write_dataframe(&df)?; 107 | excel_writer.save(filename)?; 108 | 109 | Ok(()) 110 | } 111 | 112 | #[test] 113 | fn dataframe_excelwriter13_1() { 114 | let test_runner = common::TestRunner::new() 115 | .set_name("dataframe13") 116 | .set_function(create_new_xlsx_file_1) 117 | .unique("1") 118 | .initialize(); 119 | 120 | test_runner.assert_eq(); 121 | test_runner.cleanup(); 122 | } 123 | 124 | #[test] 125 | fn dataframe_excelwriter13_2() { 126 | let test_runner = common::TestRunner::new() 127 | .set_name("dataframe13") 128 | .set_function(create_new_xlsx_file_2) 129 | .unique("2") 130 | .initialize(); 131 | 132 | test_runner.assert_eq(); 133 | test_runner.cleanup(); 134 | } 135 | 136 | #[test] 137 | fn dataframe_excelwriter13_3() { 138 | let test_runner = common::TestRunner::new() 139 | .set_name("dataframe13") 140 | .set_function(create_new_xlsx_file_3) 141 | .unique("3") 142 | .initialize(); 143 | 144 | test_runner.assert_eq(); 145 | test_runner.cleanup(); 146 | } 147 | 148 | #[test] 149 | fn dataframe_excelwriter13_4() { 150 | let test_runner = common::TestRunner::new() 151 | .set_name("dataframe13") 152 | .set_function(create_new_xlsx_file_4) 153 | .unique("4") 154 | .initialize(); 155 | 156 | test_runner.assert_eq(); 157 | test_runner.cleanup(); 158 | } 159 | 160 | #[test] 161 | fn dataframe_excelwriter13_5() { 162 | let test_runner = common::TestRunner::new() 163 | .set_name("dataframe13") 164 | .set_function(create_new_xlsx_file_5) 165 | .unique("5") 166 | .initialize(); 167 | 168 | test_runner.assert_eq(); 169 | test_runner.cleanup(); 170 | } 171 | -------------------------------------------------------------------------------- /tests/integration/dataframe14.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Format, XlsxError}; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. This file 15 | // has bold integers in the first column. 16 | 17 | // Use dtype formatting. 18 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 19 | let df: DataFrame = df!( 20 | "Foo" => &[1, 1, 1], 21 | "Bar" => &[2.4, 2.4, 2.4], 22 | )?; 23 | 24 | let mut excel_writer = PolarsExcelWriter::new(); 25 | let format = Format::new().set_num_format("0.0000"); 26 | 27 | excel_writer.set_dtype_format(DataType::Float64, format); 28 | 29 | excel_writer.write_dataframe(&df)?; 30 | excel_writer.save(filename)?; 31 | 32 | Ok(()) 33 | } 34 | 35 | // Ensure column formatting overrides dtype formatting. 36 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 37 | let df: DataFrame = df!( 38 | "Foo" => &[1, 1, 1], 39 | "Bar" => &[2.4, 2.4, 2.4], 40 | )?; 41 | 42 | let mut excel_writer = PolarsExcelWriter::new(); 43 | let format = Format::new().set_num_format("0.0000"); 44 | 45 | excel_writer.set_dtype_format(DataType::Float64, &format); 46 | excel_writer.set_column_format("Bar", &format); 47 | 48 | excel_writer.write_dataframe(&df)?; 49 | excel_writer.save(filename)?; 50 | 51 | Ok(()) 52 | } 53 | 54 | // Use column formatting. 55 | fn create_new_xlsx_file_3(filename: &str) -> Result<(), XlsxError> { 56 | let df: DataFrame = df!( 57 | "Foo" => &[1, 1, 1], 58 | "Bar" => &[2.4, 2.4, 2.4], 59 | )?; 60 | 61 | let mut excel_writer = PolarsExcelWriter::new(); 62 | let format = Format::new().set_num_format("0.0000"); 63 | 64 | excel_writer.set_column_format("Bar", format); 65 | 66 | excel_writer.write_dataframe(&df)?; 67 | excel_writer.save(filename)?; 68 | 69 | Ok(()) 70 | } 71 | 72 | // Use all float dtype formatting. 73 | fn create_new_xlsx_file_4(filename: &str) -> Result<(), XlsxError> { 74 | let df: DataFrame = df!( 75 | "Foo" => &[1, 1, 1], 76 | "Bar" => &[2.4, 2.4, 2.4], 77 | )?; 78 | 79 | let mut excel_writer = PolarsExcelWriter::new(); 80 | let format = Format::new().set_num_format("0.0000"); 81 | 82 | excel_writer.set_dtype_float_format(format); 83 | 84 | excel_writer.write_dataframe(&df)?; 85 | excel_writer.save(filename)?; 86 | 87 | Ok(()) 88 | } 89 | 90 | // Use all number dtype formatting. 91 | fn create_new_xlsx_file_5(filename: &str) -> Result<(), XlsxError> { 92 | let df: DataFrame = df!( 93 | "Foo" => &[1, 1, 1], 94 | "Bar" => &[2.4, 2.4, 2.4], 95 | )?; 96 | 97 | let mut excel_writer = PolarsExcelWriter::new(); 98 | let num_format = Format::new().set_num_format("0.0000"); 99 | let default_format = Format::new(); 100 | 101 | excel_writer.set_dtype_number_format(num_format); 102 | 103 | // Override the number format for the "Foo" column. 104 | excel_writer.set_column_format("Foo", default_format); 105 | 106 | excel_writer.write_dataframe(&df)?; 107 | excel_writer.save(filename)?; 108 | 109 | Ok(()) 110 | } 111 | 112 | // Use number precision option. 113 | fn create_new_xlsx_file_6(filename: &str) -> Result<(), XlsxError> { 114 | let df: DataFrame = df!( 115 | "Foo" => &[1, 1, 1], 116 | "Bar" => &[2.4, 2.4, 2.4], 117 | )?; 118 | 119 | let mut excel_writer = PolarsExcelWriter::new(); 120 | excel_writer.set_float_precision(4); 121 | 122 | excel_writer.write_dataframe(&df)?; 123 | excel_writer.save(filename)?; 124 | 125 | Ok(()) 126 | } 127 | 128 | #[test] 129 | fn dataframe_excelwriter14_1() { 130 | let test_runner = common::TestRunner::new() 131 | .set_name("dataframe14") 132 | .set_function(create_new_xlsx_file_1) 133 | .unique("1") 134 | .initialize(); 135 | 136 | test_runner.assert_eq(); 137 | test_runner.cleanup(); 138 | } 139 | 140 | #[test] 141 | fn dataframe_excelwriter14_2() { 142 | let test_runner = common::TestRunner::new() 143 | .set_name("dataframe14") 144 | .set_function(create_new_xlsx_file_2) 145 | .unique("2") 146 | .initialize(); 147 | 148 | test_runner.assert_eq(); 149 | test_runner.cleanup(); 150 | } 151 | 152 | #[test] 153 | fn dataframe_excelwriter14_3() { 154 | let test_runner = common::TestRunner::new() 155 | .set_name("dataframe14") 156 | .set_function(create_new_xlsx_file_3) 157 | .unique("3") 158 | .initialize(); 159 | 160 | test_runner.assert_eq(); 161 | test_runner.cleanup(); 162 | } 163 | 164 | #[test] 165 | fn dataframe_excelwriter14_4() { 166 | let test_runner = common::TestRunner::new() 167 | .set_name("dataframe14") 168 | .set_function(create_new_xlsx_file_4) 169 | .unique("4") 170 | .initialize(); 171 | 172 | test_runner.assert_eq(); 173 | test_runner.cleanup(); 174 | } 175 | 176 | #[test] 177 | fn dataframe_excelwriter14_5() { 178 | let test_runner = common::TestRunner::new() 179 | .set_name("dataframe14") 180 | .set_function(create_new_xlsx_file_5) 181 | .unique("5") 182 | .initialize(); 183 | 184 | test_runner.assert_eq(); 185 | test_runner.cleanup(); 186 | } 187 | 188 | #[test] 189 | fn dataframe_excelwriter14_6() { 190 | let test_runner = common::TestRunner::new() 191 | .set_name("dataframe14") 192 | .set_function(create_new_xlsx_file_6) 193 | .unique("6") 194 | .initialize(); 195 | 196 | test_runner.assert_eq(); 197 | test_runner.cleanup(); 198 | } 199 | -------------------------------------------------------------------------------- /tests/integration/dataframe15.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Format, XlsxError}; 13 | 14 | // Test setting a format for strings with dtype. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Col1" => &["Foo", "Foo", "Foo"], 18 | "Col2" => &[1, 2, 3], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | let format = Format::new().set_bold(); 23 | 24 | excel_writer.set_dtype_format(DataType::String, format); 25 | 26 | excel_writer.write_dataframe(&df)?; 27 | excel_writer.save(filename)?; 28 | 29 | Ok(()) 30 | } 31 | 32 | // Test setting a format for strings with column type. 33 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 34 | let df: DataFrame = df!( 35 | "Col1" => &["Foo", "Foo", "Foo"], 36 | "Col2" => &[1, 2, 3], 37 | )?; 38 | 39 | let mut excel_writer = PolarsExcelWriter::new(); 40 | let format = Format::new().set_bold(); 41 | 42 | excel_writer.set_column_format("Col1", format); 43 | 44 | excel_writer.write_dataframe(&df)?; 45 | excel_writer.save(filename)?; 46 | 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn dataframe_excelwriter15_1() { 52 | let test_runner = common::TestRunner::new() 53 | .set_name("dataframe15") 54 | .set_function(create_new_xlsx_file_1) 55 | .unique("1") 56 | .initialize(); 57 | 58 | test_runner.assert_eq(); 59 | test_runner.cleanup(); 60 | } 61 | 62 | #[test] 63 | fn dataframe_excelwriter15_2() { 64 | let test_runner = common::TestRunner::new() 65 | .set_name("dataframe15") 66 | .set_function(create_new_xlsx_file_2) 67 | .unique("2") 68 | .initialize(); 69 | 70 | test_runner.assert_eq(); 71 | test_runner.cleanup(); 72 | } 73 | -------------------------------------------------------------------------------- /tests/integration/dataframe16.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Format, XlsxError}; 13 | 14 | // Test setting a format for strings with dtype. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Col1" => &["Foo", "Foo", "Foo"], 18 | "Col2" => &[Some(1), None, Some(3)], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | let format = Format::new().set_bold(); 23 | 24 | excel_writer.set_dtype_format(DataType::Int32, format); 25 | 26 | excel_writer.write_dataframe(&df)?; 27 | excel_writer.save(filename)?; 28 | 29 | Ok(()) 30 | } 31 | 32 | // Test setting a format for strings with column type. 33 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 34 | let df: DataFrame = df!( 35 | "Col1" => &["Foo", "Foo", "Foo"], 36 | "Col2" => &[Some(1), None, Some(3)], 37 | )?; 38 | 39 | let mut excel_writer = PolarsExcelWriter::new(); 40 | let format = Format::new().set_bold(); 41 | 42 | excel_writer.set_column_format("Col2", format); 43 | 44 | excel_writer.write_dataframe(&df)?; 45 | excel_writer.save(filename)?; 46 | 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn dataframe_excelwriter16_1() { 52 | let test_runner = common::TestRunner::new() 53 | .set_name("dataframe16") 54 | .set_function(create_new_xlsx_file_1) 55 | .unique("1") 56 | .initialize(); 57 | 58 | test_runner.assert_eq(); 59 | test_runner.cleanup(); 60 | } 61 | 62 | #[test] 63 | fn dataframe_excelwriter16_2() { 64 | let test_runner = common::TestRunner::new() 65 | .set_name("dataframe16") 66 | .set_function(create_new_xlsx_file_2) 67 | .unique("2") 68 | .initialize(); 69 | 70 | test_runner.assert_eq(); 71 | test_runner.cleanup(); 72 | } 73 | -------------------------------------------------------------------------------- /tests/integration/dataframe17.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::{Format, XlsxError}; 13 | 14 | // Test setting a format for strings with dtype. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Col1" => &["Foo", "Foo", "Foo"], 18 | "Col2" => &[Some(1), None, Some(3)], 19 | )?; 20 | 21 | let mut excel_writer = PolarsExcelWriter::new(); 22 | let format = Format::new().set_bold(); 23 | 24 | excel_writer.set_dtype_format(DataType::Int32, format); 25 | excel_writer.set_null_value("NULL"); 26 | 27 | excel_writer.write_dataframe(&df)?; 28 | excel_writer.save(filename)?; 29 | 30 | Ok(()) 31 | } 32 | 33 | // Test setting a format for strings with column type. 34 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 35 | let df: DataFrame = df!( 36 | "Col1" => &["Foo", "Foo", "Foo"], 37 | "Col2" => &[Some(1), None, Some(3)], 38 | )?; 39 | 40 | let mut excel_writer = PolarsExcelWriter::new(); 41 | let format = Format::new().set_bold(); 42 | 43 | excel_writer.set_column_format("Col2", format); 44 | excel_writer.set_null_value("NULL"); 45 | 46 | excel_writer.write_dataframe(&df)?; 47 | excel_writer.save(filename)?; 48 | 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | fn dataframe_excelwriter17_1() { 54 | let test_runner = common::TestRunner::new() 55 | .set_name("dataframe17") 56 | .set_function(create_new_xlsx_file_1) 57 | .unique("1") 58 | .initialize(); 59 | 60 | test_runner.assert_eq(); 61 | test_runner.cleanup(); 62 | } 63 | 64 | #[test] 65 | fn dataframe_excelwriter17_2() { 66 | let test_runner = common::TestRunner::new() 67 | .set_name("dataframe17") 68 | .set_function(create_new_xlsx_file_2) 69 | .unique("2") 70 | .initialize(); 71 | 72 | test_runner.assert_eq(); 73 | test_runner.cleanup(); 74 | } 75 | -------------------------------------------------------------------------------- /tests/integration/dataframe18.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use chrono::{NaiveDate, NaiveTime}; 11 | use polars::prelude::*; 12 | use polars_excel_writer::PolarsExcelWriter; 13 | use rust_xlsxwriter::{Format, XlsxError}; 14 | 15 | // Compare output against target Excel file using PolarsExcelWriter. 16 | 17 | // The test case uses numbers and formats instead of dates/times. 18 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 19 | let df: DataFrame = df!( 20 | "Column1" => &[45658.5, 45658.5, 45658.5], 21 | "Column2" => &[45658.0, 45658.0, 45658.], 22 | "Column3" => &[0.5, 0.5, 0.5], 23 | )?; 24 | 25 | let mut excel_writer = PolarsExcelWriter::new(); 26 | let format1 = Format::new().set_num_format("yyyy/mm/dd\\ hh:mm"); 27 | let format2 = Format::new().set_num_format("yyyy/mm/dd"); 28 | let format3 = Format::new().set_num_format("hh\\ mm"); 29 | 30 | excel_writer.set_column_format("Column1", &format1); 31 | excel_writer.set_column_format("Column2", &format2); 32 | excel_writer.set_column_format("Column3", &format3); 33 | 34 | excel_writer.write_dataframe(&df)?; 35 | 36 | let worksheet = excel_writer.worksheet()?; 37 | worksheet.set_column_range_width_pixels(0, 2, 120)?; 38 | 39 | excel_writer.save(filename)?; 40 | 41 | Ok(()) 42 | } 43 | 44 | // The test case uses dates/times. 45 | fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { 46 | let df: DataFrame = df!( 47 | 48 | "Column1" => &[ 49 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 50 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 51 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 52 | ], 53 | "Column2" => &[ 54 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 55 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 56 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 57 | ], 58 | "Column3" => &[ 59 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 60 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 61 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 62 | ], 63 | )?; 64 | 65 | let mut excel_writer = PolarsExcelWriter::new(); 66 | let format1 = Format::new().set_num_format("yyyy/mm/dd\\ hh:mm"); 67 | let format2 = Format::new().set_num_format("yyyy/mm/dd"); 68 | let format3 = Format::new().set_num_format("hh\\ mm"); 69 | 70 | excel_writer.set_dtype_format(DataType::Datetime(TimeUnit::Milliseconds, None), &format1); 71 | excel_writer.set_dtype_format(DataType::Date, &format2); 72 | excel_writer.set_dtype_format(DataType::Time, &format3); 73 | 74 | excel_writer.write_dataframe(&df)?; 75 | 76 | let worksheet = excel_writer.worksheet()?; 77 | worksheet.set_column_range_width_pixels(0, 2, 120)?; 78 | 79 | excel_writer.save(filename)?; 80 | 81 | Ok(()) 82 | } 83 | 84 | #[test] 85 | fn dataframe_excelwriter18_1() { 86 | let test_runner = common::TestRunner::new() 87 | .set_name("dataframe18") 88 | .set_function(create_new_xlsx_file_1) 89 | .unique("1") 90 | .initialize(); 91 | 92 | test_runner.assert_eq(); 93 | test_runner.cleanup(); 94 | } 95 | 96 | #[test] 97 | fn dataframe_excelwriter18_2() { 98 | let test_runner = common::TestRunner::new() 99 | .set_name("dataframe18") 100 | .set_function(create_new_xlsx_file_2) 101 | .unique("2") 102 | .initialize(); 103 | 104 | test_runner.assert_eq(); 105 | test_runner.cleanup(); 106 | } 107 | -------------------------------------------------------------------------------- /tests/integration/dataframe19.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use chrono::{NaiveDate, NaiveTime}; 11 | use polars::prelude::*; 12 | use polars_excel_writer::PolarsExcelWriter; 13 | use rust_xlsxwriter::XlsxError; 14 | 15 | // Compare output against target Excel file using PolarsExcelWriter. 16 | 17 | // Test with default date/time formats. 18 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 19 | let df: DataFrame = df!( 20 | 21 | "Col1" => &[ 22 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 23 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 24 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(), 25 | ], 26 | "Col2" => &[ 27 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 28 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 29 | NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(), 30 | ], 31 | "Col3" => &[ 32 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 33 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 34 | NaiveTime::from_hms_opt(12, 0, 0).unwrap(), 35 | ], 36 | )?; 37 | 38 | let mut excel_writer = PolarsExcelWriter::new(); 39 | 40 | excel_writer.write_dataframe(&df)?; 41 | 42 | excel_writer.save(filename)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | fn dataframe_excelwriter19_1() { 49 | let test_runner = common::TestRunner::new() 50 | .set_name("dataframe19") 51 | .set_function(create_new_xlsx_file_1) 52 | .unique("2") 53 | .initialize(); 54 | 55 | test_runner.assert_eq(); 56 | test_runner.cleanup(); 57 | } 58 | -------------------------------------------------------------------------------- /tests/integration/dataframe20.rs: -------------------------------------------------------------------------------- 1 | // Test case that compares a file generated by polars_excel_writer with a file 2 | // created by Excel. 3 | // 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | // 6 | // Copyright 2022-2025, John McNamara, jmcnamara@cpan.org 7 | 8 | use crate::common; 9 | 10 | use polars::prelude::*; 11 | use polars_excel_writer::PolarsExcelWriter; 12 | use rust_xlsxwriter::XlsxError; 13 | 14 | // Compare output against target Excel file using PolarsExcelWriter. 15 | fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> { 16 | let df: DataFrame = df!( 17 | "Foo" => &[1, 1, 1], 18 | "Bar" => &[2, 2, 2], 19 | )?; 20 | 21 | let format = rust_xlsxwriter::Format::new().set_font_color("#FF0000"); 22 | 23 | let mut excel_writer = PolarsExcelWriter::new(); 24 | excel_writer.set_header_format(&format); 25 | 26 | excel_writer.write_dataframe(&df)?; 27 | excel_writer.save(filename)?; 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn dataframe_excelwriter20() { 34 | let test_runner = common::TestRunner::new() 35 | .set_name("dataframe20") 36 | .set_function(create_new_xlsx_file_1) 37 | .unique("1") 38 | .ignore_file("xl/styles.xml") 39 | .initialize(); 40 | 41 | test_runner.assert_eq(); 42 | test_runner.cleanup(); 43 | } 44 | -------------------------------------------------------------------------------- /tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | mod dataframe01; 4 | mod dataframe02; 5 | mod dataframe03; 6 | mod dataframe04; 7 | mod dataframe05; 8 | mod dataframe06; 9 | mod dataframe07; 10 | mod dataframe08; 11 | mod dataframe09; 12 | mod dataframe10; 13 | mod dataframe11; 14 | mod dataframe12; 15 | mod dataframe13; 16 | mod dataframe14; 17 | mod dataframe15; 18 | mod dataframe16; 19 | mod dataframe17; 20 | mod dataframe18; 21 | mod dataframe19; 22 | mod dataframe20; 23 | -------------------------------------------------------------------------------- /tests/output/.gitignore: -------------------------------------------------------------------------------- 1 | # Output dir for xlsx files created by the tests programs. 2 | # These are ignored since we only need them for testing. 3 | *.xlsx 4 | 5 | --------------------------------------------------------------------------------