diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..17b8aab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*.{groovy, java, kt, xml}] +#缩进风格:空格 +indent_style = space +#缩进大小 +indent_size = 4 +#换行符lf +end_of_line = lf +#字符集utf-8 +charset = utf-8 +#是否删除行尾的空格 +trim_trailing_whitespace = true +#是否在文件的最后插入一个空行 +insert_final_newline = true \ No newline at end of file diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md new file mode 100644 index 0000000..8d7f460 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -0,0 +1,9 @@ +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 异常代码 +```java + 这里写你的代码 +``` +# 异常提示 +大家尽量把问题一次性描述清楚,然后贴上全部异常,这样方便把问题一次性解决掉。 +# 其他描述 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..f30eacb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,20 @@ +--- +name: bug +about: 发现一个新的Bug +title: '' +labels: bug +assignees: zhuangjiaju + +--- + +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 触发场景描述 + +# 触发Bug的代码 +```java + 这里写代码 +``` +# 提示的异常或者没有达到的效果 +大家尽量把问题一次性描述清楚,然后贴上全部异常,这样方便把问题一次性解决掉。 +至少大家要符合一个原则就是,能让其他人复现出这个问题,如果无法复现,肯定无法解决。 diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..daf6eac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,19 @@ +--- +name: question +about: 有使用疑问,请先阅读“快速开始”,上面无法解决再提交问题。处理完请关闭问题。 +title: '' +labels: help wanted +assignees: '' + +--- + +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 异常代码 +```java + 这里写你的代码 +``` +# 异常提示 +大家尽量把问题一次性描述清楚,然后贴上全部异常,这样方便把问题一次性解决掉。 +至少大家要符合一个原则就是,能让其他人复现出这个问题,如果无法复现,肯定无法解决。 +# 问题描述 diff --git a/.github/ISSUE_TEMPLATE/suggest.md b/.github/ISSUE_TEMPLATE/suggest.md new file mode 100644 index 0000000..41884ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggest.md @@ -0,0 +1,12 @@ +--- +name: suggest +about: 给出一些建议的能 +title: '' +labels: suggest +assignees: '' + +--- + +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 建议描述 diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..b89c0b8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,10 @@ +## 关联的ISSUE +TODO 请说明关联的异常 +## 问题描述&解决 +TODO 描述下出了什么问题,必要的话提供excel。在PR合并前,审核同学一定会复现问题,然后再PR合并的。 +## 代码注意点 +* 确认已经安装了阿里巴巴代码规约插件:[alibaba-java-coding-guidelines ](https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines) +* src/main 下面不要出现中文注释 +* src/test/core && src/test/demo 下面要新增测试案例请严格参照其他的,包括注释等等什么都不要错 +* 如果只是自己测试 全部加入到 src/test/temp +* 如果改动量较大(5+文件)尽量先和作者沟通,这个风险很大 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f4e30b6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +# +# Copyright 2009-2021 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Java CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ 8, 11, 17, 21] + distribution: [ 'adopt' ] + fail-fast: false + max-parallel: 4 + name: Test JDK ${{ matrix.java }} + + steps: + - uses: actions/checkout@main + - name: Set up JDK + uses: actions/setup-java@main + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + - name: Cache local Maven repository + uses: actions/cache@main + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2 + - name: Chmod + run: chmod +x mvnw + - name: Test with Maven + if: ${{ matrix.java == '8' }} + run: ./mvnw test -B -Dmaven.test.skip=false + - name: Test with Maven + if: ${{ matrix.java != '8' }} + run: ./mvnw test -B -Dmaven.test.skip=false -DargLine="--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/sun.reflect.annotation=ALL-UNNAMED" + - name: Maven Build + run: ./mvnw install -B -V + - name: Java Doc + run: ./mvnw javadoc:javadoc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d6bba39 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,55 @@ +# +# Copyright 2009-2021 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Publish package to the Maven Central Repository + + + +on: + release: + types: [created] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@main + - name: Install Java and Maven + uses: actions/setup-java@main + with: + java-version: 8 + distribution: 'adopt' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + - name: Cache local Maven repository + uses: actions/cache@main + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2 + - id: install-secret-key + name: Install GPG secret key + run: | + cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import + - name: Publish package + run: | + mvn --batch-mode -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} clean deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=false -Dgpg.skip=false + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/sync2gitee.yml b/.github/workflows/sync2gitee.yml new file mode 100644 index 0000000..5b48b08 --- /dev/null +++ b/.github/workflows/sync2gitee.yml @@ -0,0 +1,26 @@ +# 通过 Github action, 在仓库的每一次 commit 后自动同步到 Gitee 上 +name: Mirror the Github organization repos to Gitee +on: [push] + +jobs: + repo-sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + with: + persist-credentials: false + - name: Mirror the Github organization repos to Gitee. + uses: Yikun/hub-mirror-action@master + with: + # 必选,需要同步的 Github 这里记住选择的是仓库 或者账号 而不是具体的项目 + src: github/alibaba + # 必选,需要同步到的 Gitee 这里记住选择的是仓库 或者账号 而不是具体的项目 + dst: gitee/easyexcel + # 必选,Gitee公钥对应的私钥,https://gitee.com/profile/sshkeys + dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} + # 必选,Gitee对应的用于创建仓库的token,https://gitee.com/profile/personal_access_tokens + dst_token: ${{ secrets.GITEE_TOKEN }} + # 如果是组织,指定组织即可,默认为用户 user + account_type: org + # 需要同步的仓库里面的项目 + static_list: "easyexcel" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93472b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.idea/ +*/.settings/ +*.idea +.DS_Store +**/.iml* +*.iml +**/.class +**/.classpath +**/.project +*/target/ +target/ +*.ipr +*.iws +antx.properties +output/ +.flattened-pom.xml +dependency-reduced-pom.xml diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c1dd12f Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..e83fa69 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..997623f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +## 前言 +非常感谢您愿意协助EasyExcel的开发,EasyExcel成长离不开大家的贡献。但是为了合作的更有效率,希望我们在贡献代码的时候能按照如下约定。 +## 提前沟通 +尽量把自己的想法和实现思路提前沟通,可以通过issue,钉钉,QQ都可以,可能很多问题我们内部已经讨论过,由于各种原因后续不会支持,但是您这边又开发好了,这样容易浪费您的时间。 +## 代码规范 +请先安装阿里巴巴代码规约插件!!!https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines +目前代码规范已经集成了自动校验,然后源代码尽量不要有中文注释。在新增功能的时候,尽量注意补充junit。core代表每次travis-ci都会跑的测试案例,然后demo用于对外看到,temp里面随便写。 +## 提交分支 +建议提交到最新的版本号.x上面,比如 3.x之类的版本,为了方便其他同学阅读源代码,所以目前的思路是master和maven center的最新版本代码保持一致,然后您提交过来的代码我们可能会稍微做一些修改。所以提交到开发分支会比较好。fork也可以直接fork该分支。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index c49ebb0..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,2 +0,0 @@ -# easyexcel-plus -easyexcel作者最新升级版本, 快速、简洁、解决大文件内存溢出的java处理Excel工具 diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..5991b71 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,157 @@ +EasyExcel +====================== +[![Build Status](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml?query=branch%3Amaster) +[![Maven central](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel) +[![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) +[![](https://img.shields.io/badge/EasyExcel-Check%20Your%20Contribution-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=easyexcel) + +[Communication Group 1 in QQ(Full): 662022184](https://jq.qq.com/?_wv=1027&k=1T21jJxh) +[Communication Group 2 in QQ(Full): 1097936804](https://jq.qq.com/?_wv=1027&k=j5zEy6Xl) +[Communication Group 3 in QQ(Full): 453928496](https://qm.qq.com/cgi-bin/qm/qr?k=e2ULsA5A0GldhV2CXJ8sIbAyu9I6qqs7&jump_from=webapi) +[Communication Group 4 in QQ: 496594404](https://qm.qq.com/cgi-bin/qm/qr?k=e_aVG1Q7gi0PJUBkbrUGAgbeO3kUEInK&jump_from=webapi) +[Communication Group 1 in DingTalk(Full): 21960511](https://qr.dingtalk.com/action/joingroup?code=v1,k1,cchz6k12ci9B08NNqhNRFGXocNVHrZtW0kaOtTKg/Rk=&_dt_no_comment=1&origin=11) +[Communication Group 2 in DingTalk(Full): 32796397](https://qr.dingtalk.com/action/joingroup?code=v1,k1,jyU9GtEuNU5S0QTyklqYcYJ8qDZtUuTPMM7uPZTS8Hs=&_dt_no_comment=1&origin=11) +[Communication Group 3 in DingTalk(Full): 33797247](https://qr.dingtalk.com/action/joingroup?code=v1,k1,3UGlEScTGQaHpW2cIRo+gkxJ9EVZ5fz26M6nW3uFP30=&_dt_no_comment=1&origin=11) +[Communication Group 4 in DingTalk(Full): 33491624](https://qr.dingtalk.com/action/joingroup?code=v1,k1,V14Pb65Too70rQkEaJ9ohb6lZBZbtp6jIL/q9EWh9vA=&_dt_no_comment=1&origin=11) +[Communication Group 5 in DingTalk: 32134498](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingb9fa1325d9dccc3ecac589edd02f1650&5233a=71a83&cbdbhh=qwertyuiop) +[Official Website: https://yuque.com/easyexcel](https://www.yuque.com/easyexcel/doc/easyexcel) + +[FAQ](https://www.yuque.com/easyexcel/faq) +#### It is recommended to join a DingTalk group + +# EasyExcel, a java toolkit for parsing Excel easily +There are several java frameworks or toolkit which can parse and generate Excel, such as Apache POI or jxl. But they all have some difficulties to handle problems like excessive memory usage. Apache POI framework has a set of SAX mode API can fix some memory overflow problems at some extent, but it still has some flaws. For example, the unzipping and the storage of the unzipping of Excel file in version 07 are done in memory, so the memory consumption is still very high. The EasyExcel toolkit rewrites the logic of POI for parsing Excel version 07. One 3 megabytes Excel file parsed with POI still requires about 100M memory, which can be reduced to a few megabyte by using EasyExcel instead. And yes, there is no memory overflow for even larger excel with EasyExcel. EasyExcel version 03 depends on POI SAX model and does model transformation/encapsulation in the upper layer to make it simpler and more convenient for users. + +## Using EasyExcel version 3.0.2+, a machine with 64M RAM can read a 75 megabyte Excel file containing 460,000 rows and 25 columns in 20 seconds +Of course, there is also a very fast mode can be faster, but the memory consumption will be a little more than 100M +![img](img/readme/large.png) + +## Version support +* EasyExcel version 2+ works on Java7 or Java6 +* EasyExcel version 3+ works on Java8 or java8+ +### About version upgrade +* It is not recommended upgrading across major versions, especially across 2 major versions. +* There are some incompatibilities in upgrading from version 2+ to version 3+. + * Using a custom interceptor to modify the style can cause problems, even if it does not compile with errors. + * When reading the Excel file, the `invoke` function will throw an exception, there will not be an additional layer of `ExcelAnalysisException` wrapped here, and it will not compile with errors. + * Style and other annotations involving `boolean` or some enumeration values have been changed, adding the default value. The compiler will report an error, just change the annotation. +* It is recommended to re-test the relevant functions after upgrading across major versions. + +### Latest Version +```xml + + com.alibaba + easyexcel + 3.0.2 + +``` + +## Advertising space +### Alibaba New Retail Business Department Recruitment +Alibaba New Retail Business Department sincerely recruit JAVA senior development, technical experts. If you are interested, you can contact us by WeChat, or send your Resume to my email jipengfei.jpf@alibaba-inc.com. +### EasyExcel personnel recruitment +Anyone who wants to participate in this project can apply, mainly responsible for answering questions in the communication group and dealing with the issues. Of course, you can also do some PR. +Since there is no material reward for participating in the open source project, and the current maintainers are also maintaining the project in their spare time. So people who want to join this project need to be persistent, not just on a whim. +The requirements are as follows: +* There are certain java coding skills & good coding habits +* Understand the read&write principles of EasyExcel +* Has passion for open source projects +* Be able to do things consistently for a long time +* Your job is not so busy + +## Related Documents +* [Quick Start](https://www.yuque.com/easyexcel/doc/easyexcel) +* [About Us](/abouteasyexcel.md) +* [Update Notes](/update.md) +* [Code Contribution](https://www.yuque.com/easyexcel/doc/contribute) + +## Maintainers +姬朋飞(玉霄)、庄家钜、怀宇 +## Quick Start +### Read Excel File +DEMO:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/demo/read/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) + +```java + /** + * The easiest way to read Excel file using EasyExcel toolkit + * + *

+ * 1. Create an entity object, such as {@link DemoData}, each property of the entity object corresponds to a specific field in any row of Excel. + * 2. When reading each row of an Excel file, create a callback listener for the corresponding row. Refer to{@link DemoDataListener} + * 3. Invoke the read function + *

+ */ + @Test + public void simpleRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // Specify which entity object class to use to read the Excel content. The file stream will close automatically after reading the first sheet of Excel. + EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); + } +``` + +### Write Excel File +DEMO:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java) +```java + /** + * The easiest way to write Excel file using EasyExcel toolkit + * + *

+ * 1. Create an entity object, refer to{@link write.demo.plus.easyexcel.test.DemoData}. + * Each property of the entity object corresponds to a specific field of Excel + * 2. Invoke write function + *

+ */ + @Test + public void simpleWrite() { + String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + // Specify which entity object class to use to write Excel, it will write to the first sheet of Excel with the name template. Then the file stream will be closed automatically. + // With version 03, just pass in the excelType parameter + EasyExcel.write(fileName, DemoData.class).sheet("template").doWrite(data()); + } +``` + +### File Uploading&Downloading +DEMO:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) +```java + /** + * File downloading + * + * Note: returns an Excel with partial data if it fails + * + *

+ * 1. Create an entity object, refer to{@link DownloadData}. + * Each property of the entity object corresponds to a specific field of Excel + * 2. Specify the returned properties + * 3. Invoke write function, then the OutputStream is automatically closed when it ends. + *

+ */ + @GetMapping("download") + public void download(HttpServletResponse response) throws IOException { + // Using swagger may cause some problems, please use your browser directly or use postman to invoke this + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + // URLEncoder.encode function can prevent Chinese garbled code + String fileName = URLEncoder.encode("test", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("template").doWrite(data()); + } + + /** + * File uploading + * + *

+ * 1. Create an entity object, refer to{@link UploadData} + * Each property of the entity object corresponds to a specific field of Excel + * 2. When reading each row of an Excel file, create a callback listener for the corresponding row. Refer to{@link UploadDataListener} + * 3. Invoke read function + *

+ */ + @PostMapping("upload") + @ResponseBody + public String upload(MultipartFile file) throws IOException { + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); + return "success"; + } +``` +### Contact Us +If you have any questions, Alibaba colleagues can find me in DingTalk, and others can leave messages here. All related questions are well welcomed. diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..1363584 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,65 @@ +# 详细参数介绍 +## 关于常见类解析 +* EasyExcel 入口类,用于构建开始各种操作 +* ExcelReaderBuilder ExcelWriterBuilder 构建出一个 ReadWorkbook WriteWorkbook,可以理解成一个excel对象,一个excel只要构建一个 +* ExcelReaderSheetBuilder ExcelWriterSheetBuilder 构建出一个 ReadSheet WriteSheet对象,可以理解成excel里面的一页,每一页都要构建一个 +* ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据 +* WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据 +* 所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel...sheet()方法之前作用域是整个sheet,之后针对单个sheet +## 读 +### 注解 +* `ExcelProperty` 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。 +* `ExcelIgnore` 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段 +* `DateTimeFormat` 日期转换,用`String`去接收excel日期格式的数据会调用这个注解。里面的`value`参照`java.text.SimpleDateFormat` +* `NumberFormat` 数字转换,用`String`去接收excel数字格式的数据会调用这个注解。里面的`value`参照`java.text.DecimalFormat` +* `ExcelIgnoreUnannotated` 默认不加`ExcelProperty` 的注解的都会参与读写,加了不会参与 +### 参数 +#### 通用参数 +`ReadWorkbook`,`ReadSheet` 都会有的参数,如果为空,默认使用上级。 +* `converter` 转换器,默认加载了很多转换器。也可以自定义。 +* `readListener` 监听器,在读取数据的过程中会不断的调用监听器。 +* `headRowNumber` 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。 +* `head` 与`clazz`二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class。 +* `clazz` 与`head`二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。 +* `autoTrim` 字符串、表头等数据自动trim +* `password` 读的时候是否需要使用密码 +#### ReadWorkbook(理解成excel对象)参数 +* `excelType` 当前excel的类型 默认会自动判断 +* `inputStream` 与`file`二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用`file`参数。因为使用了`inputStream` easyexcel会帮忙创建临时文件,最终还是`file` +* `file` 与`inputStream`二选一。读取文件的文件。 +* `autoCloseStream` 自动关闭流。 +* `readCache` 默认小于5M用 内存,超过5M会使用 `EhCache`,这里不建议使用这个参数。 +#### ReadSheet(就是excel的一个Sheet)参数 +* `sheetNo` 需要读取Sheet的编码,建议使用这个来指定读取哪个Sheet +* `sheetName` 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配 +## 写 +### 注解 +* `ExcelProperty` index 指定写到第几列,默认根据成员变量排序。`value`指定写入的名称,默认成员变量的名字,多个`value`可以参照快速开始中的复杂头 +* `ExcelIgnore` 默认所有字段都会写入excel,这个注解会忽略这个字段 +* `DateTimeFormat` 日期转换,将`Date`写到excel会调用这个注解。里面的`value`参照`java.text.SimpleDateFormat` +* `NumberFormat` 数字转换,用`Number`写excel会调用这个注解。里面的`value`参照`java.text.DecimalFormat` +* `ExcelIgnoreUnannotated` 默认不加`ExcelProperty` 的注解的都会参与读写,加了不会参与 +### 参数 +#### 通用参数 +`WriteWorkbook`,`WriteSheet` ,`WriteTable`都会有的参数,如果为空,默认使用上级。 +* `converter` 转换器,默认加载了很多转换器。也可以自定义。 +* `writeHandler` 写的处理器。可以实现`WorkbookWriteHandler`,`SheetWriteHandler`,`RowWriteHandler`,`CellWriteHandler`,在写入excel的不同阶段会调用 +* `relativeHeadRowIndex` 距离多少行后开始。也就是开头空几行 +* `needHead` 是否导出头 +* `head` 与`clazz`二选一。写入文件的头列表,建议使用class。 +* `clazz` 与`head`二选一。写入文件的头对应的class,也可以使用注解。 +* `autoTrim` 字符串、表头等数据自动trim +#### WriteWorkbook(理解成excel对象)参数 +* `excelType` 当前excel的类型 默认`xlsx` +* `outputStream` 与`file`二选一。写入文件的流 +* `file` 与`outputStream`二选一。写入的文件 +* `templateInputStream` 模板的文件流 +* `templateFile` 模板文件 +* `autoCloseStream` 自动关闭流。 +* `password` 写的时候是否需要使用密码 +* `useDefaultStyle` 写的时候是否是使用默认头 +#### WriteSheet(就是excel的一个Sheet)参数 +* `sheetNo` 需要写入的编码。默认0 +* `sheetName` 需要些的Sheet名称,默认同`sheetNo` +#### WriteTable(就把excel的一个Sheet,一块区域看一个table)参数 +* `tableNo` 需要写入的编码。默认0 \ No newline at end of file diff --git a/docs/LARGEREAD.md b/docs/LARGEREAD.md new file mode 100644 index 0000000..8614727 --- /dev/null +++ b/docs/LARGEREAD.md @@ -0,0 +1,26 @@ +# 10M以上文件读取说明 +03版没有办法处理,相对内存占用大很多。excel 07版本有个共享字符串[共享字符串](https://docs.microsoft.com/zh-cn/office/open-xml/working-with-the-shared-string-table)的概念,这个会非常占用内存,如果全部读取到内存的话,大概是excel文件的大小的3-10倍,所以easyexcel用存储文件的,然后再反序列化去读取的策略来节约内存。当然需要通过文件反序列化以后,效率会降低,大概降低30-50%(不一定,也看命中率,可能会超过100%) +## 如果对读取效率感觉还能接受,就用默认的,永久占用(单个excel读取整个过程)一般不会超过50M(大概率就30M),剩下临时的GC会很快回收 +## 默认大文件处理 +默认大文件处理会自动判断,共享字符串5M以下会使用内存存储,大概占用15-50M的内存,超过5M则使用文件存储,然后文件存储也要设置多内存M用来存放临时的共享字符串,默认20M。除了共享字符串占用内存外,其他占用较少,所以可以预估10M,所以默认大概30M就能读取一个超级大的文件。 +## 根据实际需求配置内存 +想自定义设置,首先要确定你大概愿意花多少内存来读取一个超级大的excel,比如希望读取excel最多占用100M内存(是读取过程中永久占用,新生代马上回收的不算),那就设置使用文件来存储共享字符串的大小判断为20M(小于20M存内存,大于存临时文件),然后设置文件存储时临时共享字符串占用内存大小90M差不多 +### 如果最大文件条数也就十几二十万,然后excel也就是十几二十M,而且不会有很高的并发,并且内存也较大 +```java + // 强制使用内存存储,这样大概一个20M的excel使用150M(很多临时对象,所以100M会一直GC)的内存 +// 这样效率会比上面的复杂的策略高很多 + // 这里再说明下 就是加了个readCache(new MapCache()) 参数而已,其他的参照其他demo写 这里没有写全 + EasyExcel.read().readCache(new MapCache()); +``` +### 对并发要求较高,而且都是经常有超级大文件 +```java + // 第一个参数的意思是 多少M共享字符串以后 采用文件存储 单位MB 默认5M +// 第二个参数 文件存储时,内存存放多少M缓存数据 默认20M +// 比如 你希望用100M内存(这里说的是解析过程中的永久占用,临时对象不算)来解析excel,前面算过了 大概是 20M+90M 所以设置参数为:20 和 90 + // 这里再说明下 就是加了个readCacheSelector(new SimpleReadCacheSelector(5, 20))参数而已,其他的参照其他demo写 这里没有写全 +EasyExcel.read().readCacheSelector(new SimpleReadCacheSelector(5, 20)); +``` +### 关于maxCacheActivateSize 也就是前面第二个参数的详细说明 +easyexcel在使用文件存储的时候,会把共享字符串拆分成1000条一批,然后放到文件存储。然后excel来读取共享字符串大概率是按照顺序的,所以默认20M的1000条的数据放在内存,命中后直接返回,没命中去读文件。所以不能设置太小,太小了,很难命中,一直去读取文件,太大了的话会占用过多的内存。 +### 如何判断 maxCacheActivateSize是否需要调整 +开启debug日志会输出`Already put :4000000` 最后一次输出,大概可以得出值为400W,然后看`Cache misses count:4001`得到值为4K,400W/4K=1000 这代表已经`maxCacheActivateSize` 已经非常合理了。如果小于500 问题就非常大了,500到1000 应该都还行。 diff --git a/easyexcel-plus-core/pom.xml b/easyexcel-plus-core/pom.xml new file mode 100644 index 0000000..b1cc433 --- /dev/null +++ b/easyexcel-plus-core/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + ai.chat2db.excel + easyexcel-plus-parent + 1.0.0-SNAPSHOT + ../pom.xml + + + https://github.com/CodePhiliaX/easyexcel-plus + jar + easyexcel-plus-core + easyexcel-plus-core + + + + org.apache.commons + commons-csv + 1.11.0 + + + + org.apache.poi + poi + 5.2.5 + + + org.apache.poi + poi-ooxml + 5.2.5 + + + + org.ehcache + ehcache + 3.9.11 + + + commons-io + commons-io + 2.16.1 + + + org.slf4j + slf4j-api + 1.7.36 + + + + + org.projectlombok + lombok + 1.18.32 + + + ai.chat2db.excel + easyexcel-plus-support + 1.0.1-SNAPSHOT + + + diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcel.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcel.java new file mode 100644 index 0000000..4f7ac32 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcel.java @@ -0,0 +1,8 @@ +package ai.chat2db.excel; + +/** + * This is actually {@link EasyExcelFactory}, and short names look better. + * + * @author jipengfei + */ +public class EasyExcel extends EasyExcelFactory {} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcelFactory.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcelFactory.java new file mode 100644 index 0000000..c97c6b9 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/EasyExcelFactory.java @@ -0,0 +1,356 @@ +package ai.chat2db.excel; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; + +import ai.chat2db.excel.read.builder.ExcelReaderBuilder; +import ai.chat2db.excel.read.builder.ExcelReaderSheetBuilder; +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.write.builder.ExcelWriterBuilder; +import ai.chat2db.excel.write.builder.ExcelWriterSheetBuilder; +import ai.chat2db.excel.write.builder.ExcelWriterTableBuilder; + +/** + * Reader and writer factory class + * + * @author jipengfei + */ +public class EasyExcelFactory { + + /** + * Build excel the write + * + * @return + */ + public static ExcelWriterBuilder write() { + return new ExcelWriterBuilder(); + } + + /** + * Build excel the write + * + * @param file File to write + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(File file) { + return write(file, null); + } + + /** + * Build excel the write + * + * @param file File to write + * @param head Annotate the class for configuration information + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(File file, Class head) { + ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder(); + excelWriterBuilder.file(file); + if (head != null) { + excelWriterBuilder.head(head); + } + return excelWriterBuilder; + } + + /** + * Build excel the write + * + * @param pathName File path to write + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(String pathName) { + return write(pathName, null); + } + + /** + * Build excel the write + * + * @param pathName File path to write + * @param head Annotate the class for configuration information + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(String pathName, Class head) { + ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder(); + excelWriterBuilder.file(pathName); + if (head != null) { + excelWriterBuilder.head(head); + } + return excelWriterBuilder; + } + + /** + * Build excel the write + * + * @param outputStream Output stream to write + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(OutputStream outputStream) { + return write(outputStream, null); + } + + /** + * Build excel the write + * + * @param outputStream Output stream to write + * @param head Annotate the class for configuration information. + * @return Excel writer builder + */ + public static ExcelWriterBuilder write(OutputStream outputStream, Class head) { + ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder(); + excelWriterBuilder.file(outputStream); + if (head != null) { + excelWriterBuilder.head(head); + } + return excelWriterBuilder; + } + + /** + * Build excel the writerSheet + * + * @return Excel sheet writer builder + */ + public static ExcelWriterSheetBuilder writerSheet() { + return writerSheet(null, null); + } + + /** + * Build excel the writerSheet + * + * @param sheetNo Index of sheet,0 base. + * @return Excel sheet writer builder. + */ + public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo) { + return writerSheet(sheetNo, null); + } + + /** + * Build excel the 'writerSheet' + * + * @param sheetName The name of sheet. + * @return Excel sheet writer builder. + */ + public static ExcelWriterSheetBuilder writerSheet(String sheetName) { + return writerSheet(null, sheetName); + } + + /** + * Build excel the 'writerSheet' + * + * @param sheetNo Index of sheet,0 base. + * @param sheetName The name of sheet. + * @return Excel sheet writer builder. + */ + public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo, String sheetName) { + ExcelWriterSheetBuilder excelWriterSheetBuilder = new ExcelWriterSheetBuilder(); + if (sheetNo != null) { + excelWriterSheetBuilder.sheetNo(sheetNo); + } + if (sheetName != null) { + excelWriterSheetBuilder.sheetName(sheetName); + } + return excelWriterSheetBuilder; + } + + /** + * Build excel the writerTable + * + * @return Excel table writer builder. + */ + public static ExcelWriterTableBuilder writerTable() { + return writerTable(null); + } + + /** + * Build excel the 'writerTable' + * + * @param tableNo Index of table,0 base. + * @return Excel table writer builder. + */ + public static ExcelWriterTableBuilder writerTable(Integer tableNo) { + ExcelWriterTableBuilder excelWriterTableBuilder = new ExcelWriterTableBuilder(); + if (tableNo != null) { + excelWriterTableBuilder.tableNo(tableNo); + } + return excelWriterTableBuilder; + } + + /** + * Build excel the read + * + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read() { + return new ExcelReaderBuilder(); + } + + /** + * Build excel the read + * + * @param file File to read. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(File file) { + return read(file, null, null); + } + + /** + * Build excel the read + * + * @param file File to read. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(File file, ReadListener readListener) { + return read(file, null, readListener); + } + + /** + * Build excel the read + * + * @param file File to read. + * @param head Annotate the class for configuration information. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(File file, Class head, ReadListener readListener) { + ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder(); + excelReaderBuilder.file(file); + if (head != null) { + excelReaderBuilder.head(head); + } + if (readListener != null) { + excelReaderBuilder.registerReadListener(readListener); + } + return excelReaderBuilder; + } + + /** + * Build excel the read + * + * @param pathName File path to read. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(String pathName) { + return read(pathName, null, null); + } + + /** + * Build excel the read + * + * @param pathName File path to read. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(String pathName, ReadListener readListener) { + return read(pathName, null, readListener); + } + + /** + * Build excel the read + * + * @param pathName File path to read. + * @param head Annotate the class for configuration information. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) { + ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder(); + excelReaderBuilder.file(pathName); + if (head != null) { + excelReaderBuilder.head(head); + } + if (readListener != null) { + excelReaderBuilder.registerReadListener(readListener); + } + return excelReaderBuilder; + } + + /** + * Build excel the read + * + * @param inputStream Input stream to read. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(InputStream inputStream) { + return read(inputStream, null, null); + } + + /** + * Build excel the read + * + * @param inputStream Input stream to read. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(InputStream inputStream, ReadListener readListener) { + return read(inputStream, null, readListener); + } + + /** + * Build excel the read + * + * @param inputStream Input stream to read. + * @param head Annotate the class for configuration information. + * @param readListener Read listener. + * @return Excel reader builder. + */ + public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener) { + ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder(); + excelReaderBuilder.file(inputStream); + if (head != null) { + excelReaderBuilder.head(head); + } + if (readListener != null) { + excelReaderBuilder.registerReadListener(readListener); + } + return excelReaderBuilder; + } + + /** + * Build excel the 'readSheet' + * + * @return Excel sheet reader builder. + */ + public static ExcelReaderSheetBuilder readSheet() { + return readSheet(null, null); + } + + /** + * Build excel the 'readSheet' + * + * @param sheetNo Index of sheet,0 base. + * @return Excel sheet reader builder. + */ + public static ExcelReaderSheetBuilder readSheet(Integer sheetNo) { + return readSheet(sheetNo, null); + } + + /** + * Build excel the 'readSheet' + * + * @param sheetName The name of sheet. + * @return Excel sheet reader builder. + */ + public static ExcelReaderSheetBuilder readSheet(String sheetName) { + return readSheet(null, sheetName); + } + + /** + * Build excel the 'readSheet' + * + * @param sheetNo Index of sheet,0 base. + * @param sheetName The name of sheet. + * @return Excel sheet reader builder. + */ + public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName) { + ExcelReaderSheetBuilder excelReaderSheetBuilder = new ExcelReaderSheetBuilder(); + if (sheetNo != null) { + excelReaderSheetBuilder.sheetNo(sheetNo); + } + if (sheetName != null) { + excelReaderSheetBuilder.sheetName(sheetName); + } + return excelReaderSheetBuilder; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelReader.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelReader.java new file mode 100644 index 0000000..93d1061 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelReader.java @@ -0,0 +1,123 @@ +package ai.chat2db.excel; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.analysis.ExcelAnalyser; +import ai.chat2db.excel.analysis.ExcelAnalyserImpl; +import ai.chat2db.excel.analysis.ExcelReadExecutor; +import ai.chat2db.excel.context.AnalysisContext; + +import lombok.extern.slf4j.Slf4j; + +/** + * Excel readers are all read in event mode. + * + * @author jipengfei + */ +@Slf4j +public class ExcelReader implements Closeable { + + /** + * Analyser + */ + private final ExcelAnalyser excelAnalyser; + + public ExcelReader(ReadWorkbook readWorkbook) { + excelAnalyser = new ExcelAnalyserImpl(readWorkbook); + } + + /** + * Parse all sheet content by default + * + * @deprecated lease use {@link #readAll()} + */ + @Deprecated + public void read() { + readAll(); + } + + /*** + * Parse all sheet content by default + */ + public void readAll() { + excelAnalyser.analysis(null, Boolean.TRUE); + } + + /** + * Parse the specified sheet,SheetNo start from 0 + * + * @param readSheet Read sheet + */ + public ExcelReader read(ReadSheet... readSheet) { + return read(Arrays.asList(readSheet)); + } + + /** + * Read multiple sheets. + * + * @param readSheetList + * @return + */ + public ExcelReader read(List readSheetList) { + excelAnalyser.analysis(readSheetList, Boolean.FALSE); + return this; + } + + /** + * Context for the entire execution process + * + * @return + */ + public AnalysisContext analysisContext() { + return excelAnalyser.analysisContext(); + } + + /** + * Current executor + * + * @return + */ + public ExcelReadExecutor excelExecutor() { + return excelAnalyser.excelExecutor(); + } + + /** + * @return + * @deprecated please use {@link #analysisContext()} + */ + @Deprecated + public AnalysisContext getAnalysisContext() { + return analysisContext(); + } + + /** + * Complete the entire read file.Release the cache and close stream. + */ + public void finish() { + if (excelAnalyser != null) { + excelAnalyser.finish(); + } + } + + @Override + public void close() { + finish(); + } + + /** + * Prevents calls to {@link #finish} from freeing the cache + * + */ + @Override + protected void finalize() { + try { + finish(); + } catch (Throwable e) { + log.warn("Destroy object failed", e); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelWriter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelWriter.java new file mode 100644 index 0000000..bc6acd8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/ExcelWriter.java @@ -0,0 +1,172 @@ +package ai.chat2db.excel; + +import java.io.Closeable; +import java.util.Collection; +import java.util.function.Supplier; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.write.ExcelBuilder; +import ai.chat2db.excel.write.ExcelBuilderImpl; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.WriteWorkbook; +import ai.chat2db.excel.write.metadata.fill.FillConfig; + +import lombok.extern.slf4j.Slf4j; + +/** + * Excel Writer This tool is used to write value out to Excel via POI. This object can perform the following two + * functions. + * + *
+ *    1. Create a new empty Excel workbook, write the value to the stream after the value is filled.
+ *    2. Edit existing Excel, write the original Excel file, or write it to other places.}
+ * 
+ * + * @author jipengfei + */ +@Slf4j +public class ExcelWriter implements Closeable { + + private final ExcelBuilder excelBuilder; + + /** + * Create new writer + * + * @param writeWorkbook + */ + public ExcelWriter(WriteWorkbook writeWorkbook) { + excelBuilder = new ExcelBuilderImpl(writeWorkbook); + } + + /** + * Write data to a sheet + * + * @param data Data to be written + * @param writeSheet Write to this sheet + * @return this current writer + */ + public ExcelWriter write(Collection data, WriteSheet writeSheet) { + return write(data, writeSheet, null); + } + + /** + * Write data to a sheet + * + * @param supplier Data to be written + * @param writeSheet Write to this sheet + * @return this current writer + */ + public ExcelWriter write(Supplier> supplier, WriteSheet writeSheet) { + return write(supplier.get(), writeSheet, null); + } + + /** + * Write value to a sheet + * + * @param data Data to be written + * @param writeSheet Write to this sheet + * @param writeTable Write to this table + * @return this + */ + public ExcelWriter write(Collection data, WriteSheet writeSheet, WriteTable writeTable) { + excelBuilder.addContent(data, writeSheet, writeTable); + return this; + } + + /** + * Write value to a sheet + * + * @param supplier Data to be written + * @param writeSheet Write to this sheet + * @param writeTable Write to this table + * @return this + */ + public ExcelWriter write(Supplier> supplier, WriteSheet writeSheet, WriteTable writeTable) { + excelBuilder.addContent(supplier.get(), writeSheet, writeTable); + return this; + } + + /** + * Fill value to a sheet + * + * @param data + * @param writeSheet + * @return + */ + public ExcelWriter fill(Object data, WriteSheet writeSheet) { + return fill(data, null, writeSheet); + } + + /** + * Fill value to a sheet + * + * @param data + * @param fillConfig + * @param writeSheet + * @return + */ + public ExcelWriter fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) { + excelBuilder.fill(data, fillConfig, writeSheet); + return this; + } + + /** + * Fill value to a sheet + * + * @param supplier + * @param writeSheet + * @return + */ + public ExcelWriter fill(Supplier supplier, WriteSheet writeSheet) { + return fill(supplier.get(), null, writeSheet); + } + + /** + * Fill value to a sheet + * + * @param supplier + * @param fillConfig + * @param writeSheet + * @return + */ + public ExcelWriter fill(Supplier supplier, FillConfig fillConfig, WriteSheet writeSheet) { + excelBuilder.fill(supplier.get(), fillConfig, writeSheet); + return this; + } + + /** + * Close IO + */ + public void finish() { + if (excelBuilder != null) { + excelBuilder.finish(false); + } + } + + /** + * The context of the entire writing process + * + * @return + */ + public WriteContext writeContext() { + return excelBuilder.writeContext(); + } + + @Override + public void close() { + finish(); + } + + /** + * Prevents calls to {@link #finish} from freeing the cache + */ + @Override + protected void finalize() { + try { + finish(); + } catch (Throwable e) { + log.warn("Destroy object failed", e); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyser.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyser.java new file mode 100644 index 0000000..d37f19e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyser.java @@ -0,0 +1,43 @@ +package ai.chat2db.excel.analysis; + +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.context.AnalysisContext; + +/** + * Excel file analyser + * + * @author jipengfei + */ +public interface ExcelAnalyser { + /** + * parse the sheet + * + * @param readSheetList + * Which sheets you need to read. + * @param readAll + * The readSheetList parameter is ignored, and all sheets are read. + */ + void analysis(List readSheetList, Boolean readAll); + + /** + * Complete the entire read file.Release the cache and close stream + */ + void finish(); + + /** + * Acquisition excel executor + * + * @return Excel file Executor + */ + ExcelReadExecutor excelExecutor(); + + /** + * get the analysis context. + * + * @return analysis context + */ + AnalysisContext analysisContext(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyserImpl.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyserImpl.java new file mode 100644 index 0000000..0f5e044 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelAnalyserImpl.java @@ -0,0 +1,236 @@ +package ai.chat2db.excel.analysis; + +import ai.chat2db.excel.analysis.v07.XlsxSaxAnalyser; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelAnalysisStopException; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; +import ai.chat2db.excel.analysis.csv.CsvExcelReadExecutor; +import ai.chat2db.excel.analysis.v03.XlsSaxAnalyser; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.context.csv.CsvReadContext; +import ai.chat2db.excel.context.csv.DefaultCsvReadContext; +import ai.chat2db.excel.context.xls.DefaultXlsReadContext; +import ai.chat2db.excel.context.xls.XlsReadContext; +import ai.chat2db.excel.context.xlsx.DefaultXlsxReadContext; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.NumberDataFormatterUtils; +import ai.chat2db.excel.util.StringUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.poifs.crypt.Decryptor; +import org.apache.poi.poifs.filesystem.DocumentFactoryHelper; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.List; + +/** + * @author jipengfei + */ +public class ExcelAnalyserImpl implements ExcelAnalyser { + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelAnalyserImpl.class); + + private AnalysisContext analysisContext; + + private ExcelReadExecutor excelReadExecutor; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; + + public ExcelAnalyserImpl(ReadWorkbook readWorkbook) { + try { + choiceExcelExecutor(readWorkbook); + } catch (RuntimeException e) { + finish(); + throw e; + } catch (Throwable e) { + finish(); + throw new ExcelAnalysisException(e); + } + } + + private void choiceExcelExecutor(ReadWorkbook readWorkbook) throws Exception { + ExcelTypeEnum excelType = ExcelTypeEnum.valueOf(readWorkbook); + switch (excelType) { + case XLS: + POIFSFileSystem poifsFileSystem; + if (readWorkbook.getFile() != null) { + poifsFileSystem = new POIFSFileSystem(readWorkbook.getFile()); + } else { + poifsFileSystem = new POIFSFileSystem(readWorkbook.getInputStream()); + } + // So in encrypted excel, it looks like XLS but it's actually XLSX + if (poifsFileSystem.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) { + InputStream decryptedStream = null; + try { + decryptedStream = DocumentFactoryHelper + .getDecryptedStream(poifsFileSystem.getRoot().getFileSystem(), readWorkbook.getPassword()); + XlsxReadContext xlsxReadContext = new DefaultXlsxReadContext(readWorkbook, ExcelTypeEnum.XLSX); + analysisContext = xlsxReadContext; + excelReadExecutor = new XlsxSaxAnalyser(xlsxReadContext, decryptedStream); + return; + } finally { + IOUtils.closeQuietly(decryptedStream); + // as we processed the full stream already, we can close the filesystem here + // otherwise file handles are leaked + poifsFileSystem.close(); + } + } + if (readWorkbook.getPassword() != null) { + Biff8EncryptionKey.setCurrentUserPassword(readWorkbook.getPassword()); + } + XlsReadContext xlsReadContext = new DefaultXlsReadContext(readWorkbook, ExcelTypeEnum.XLS); + xlsReadContext.xlsReadWorkbookHolder().setPoifsFileSystem(poifsFileSystem); + analysisContext = xlsReadContext; + excelReadExecutor = new XlsSaxAnalyser(xlsReadContext); + break; + case XLSX: + XlsxReadContext xlsxReadContext = new DefaultXlsxReadContext(readWorkbook, ExcelTypeEnum.XLSX); + analysisContext = xlsxReadContext; + excelReadExecutor = new XlsxSaxAnalyser(xlsxReadContext, null); + break; + case CSV: + CsvReadContext csvReadContext = new DefaultCsvReadContext(readWorkbook, ExcelTypeEnum.CSV); + analysisContext = csvReadContext; + excelReadExecutor = new CsvExcelReadExecutor(csvReadContext); + break; + default: + break; + } + } + + @Override + public void analysis(List readSheetList, Boolean readAll) { + try { + if (!readAll && CollectionUtils.isEmpty(readSheetList)) { + throw new IllegalArgumentException("Specify at least one read sheet."); + } + analysisContext.readWorkbookHolder().setParameterSheetDataList(readSheetList); + analysisContext.readWorkbookHolder().setReadAll(readAll); + try { + excelReadExecutor.execute(); + } catch (ExcelAnalysisStopException e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Custom stop!"); + } + } + } catch (RuntimeException e) { + finish(); + throw e; + } catch (Throwable e) { + finish(); + throw new ExcelAnalysisException(e); + } + } + + @Override + public void finish() { + if (finished) { + return; + } + finished = true; + if (analysisContext == null || analysisContext.readWorkbookHolder() == null) { + return; + } + ReadWorkbookHolder readWorkbookHolder = analysisContext.readWorkbookHolder(); + + Throwable throwable = null; + + try { + if (readWorkbookHolder.getReadCache() != null) { + readWorkbookHolder.getReadCache().destroy(); + } + } catch (Throwable t) { + throwable = t; + } + try { + if ((readWorkbookHolder instanceof XlsxReadWorkbookHolder) + && ((XlsxReadWorkbookHolder) readWorkbookHolder).getOpcPackage() != null) { + ((XlsxReadWorkbookHolder) readWorkbookHolder).getOpcPackage().revert(); + } + } catch (Throwable t) { + throwable = t; + } + try { + if ((readWorkbookHolder instanceof XlsReadWorkbookHolder) + && ((XlsReadWorkbookHolder) readWorkbookHolder).getPoifsFileSystem() != null) { + ((XlsReadWorkbookHolder) readWorkbookHolder).getPoifsFileSystem().close(); + } + } catch (Throwable t) { + throwable = t; + } + + // close csv. + // https://github.com/alibaba/easyexcel/issues/2309 + try { + if ((readWorkbookHolder instanceof CsvReadWorkbookHolder) + && ((CsvReadWorkbookHolder) readWorkbookHolder).getCsvParser() != null + && analysisContext.readWorkbookHolder().getAutoCloseStream()) { + ((CsvReadWorkbookHolder) readWorkbookHolder).getCsvParser().close(); + } + } catch (Throwable t) { + throwable = t; + } + + try { + if (analysisContext.readWorkbookHolder().getAutoCloseStream() + && readWorkbookHolder.getInputStream() != null) { + readWorkbookHolder.getInputStream().close(); + } + } catch (Throwable t) { + throwable = t; + } + try { + if (readWorkbookHolder.getTempFile() != null) { + FileUtils.delete(readWorkbookHolder.getTempFile()); + } + } catch (Throwable t) { + throwable = t; + } + + clearEncrypt03(); + + removeThreadLocalCache(); + + if (throwable != null) { + throw new ExcelAnalysisException("Can not close IO.", throwable); + } + } + + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); + ClassUtils.removeThreadLocalCache(); + } + + private void clearEncrypt03() { + if (StringUtils.isEmpty(analysisContext.readWorkbookHolder().getPassword()) + || !ExcelTypeEnum.XLS.equals(analysisContext.readWorkbookHolder().getExcelType())) { + return; + } + Biff8EncryptionKey.setCurrentUserPassword(null); + } + + @Override + public ExcelReadExecutor excelExecutor() { + return excelReadExecutor; + } + + @Override + public AnalysisContext analysisContext() { + return analysisContext; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelReadExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelReadExecutor.java new file mode 100644 index 0000000..24d307c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/ExcelReadExecutor.java @@ -0,0 +1,25 @@ +package ai.chat2db.excel.analysis; + +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadSheet; + +/** + * Excel file Executor + * + * @author Jiaju Zhuang + */ +public interface ExcelReadExecutor { + + /** + * Returns the actual sheet in excel + * + * @return Actual sheet in excel + */ + List sheetList(); + + /** + * Read the sheet. + */ + void execute(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/csv/CsvExcelReadExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/csv/CsvExcelReadExecutor.java new file mode 100644 index 0000000..98315f8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/csv/CsvExcelReadExecutor.java @@ -0,0 +1,147 @@ +package ai.chat2db.excel.analysis.csv; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.analysis.ExcelReadExecutor; +import ai.chat2db.excel.enums.ByteOrderMarkEnum; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelAnalysisStopSheetException; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; +import ai.chat2db.excel.util.SheetUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.context.csv.CsvReadContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.io.input.BOMInputStream; + +/** + * read executor + * + * @author zhuangjiaju + */ +@Slf4j +public class CsvExcelReadExecutor implements ExcelReadExecutor { + + private final List sheetList; + private final CsvReadContext csvReadContext; + + public CsvExcelReadExecutor(CsvReadContext csvReadContext) { + this.csvReadContext = csvReadContext; + sheetList = new ArrayList<>(); + ReadSheet readSheet = new ReadSheet(); + sheetList.add(readSheet); + readSheet.setSheetNo(0); + } + + @Override + public List sheetList() { + return sheetList; + } + + @Override + public void execute() { + CSVParser csvParser; + try { + csvParser = csvParser(); + csvReadContext.csvReadWorkbookHolder().setCsvParser(csvParser); + } catch (IOException e) { + throw new ExcelAnalysisException(e); + } + for (ReadSheet readSheet : sheetList) { + readSheet = SheetUtils.match(readSheet, csvReadContext); + if (readSheet == null) { + continue; + } + try { + csvReadContext.currentSheet(readSheet); + + int rowIndex = 0; + + for (CSVRecord record : csvParser) { + dealRecord(record, rowIndex++); + } + } catch (ExcelAnalysisStopSheetException e) { + if (log.isDebugEnabled()) { + log.debug("Custom stop!", e); + } + } + + // The last sheet is read + csvReadContext.analysisEventProcessor().endSheet(csvReadContext); + } + } + + private CSVParser csvParser() throws IOException { + CsvReadWorkbookHolder csvReadWorkbookHolder = csvReadContext.csvReadWorkbookHolder(); + CSVFormat csvFormat = csvReadWorkbookHolder.getCsvFormat(); + ByteOrderMarkEnum byteOrderMark = ByteOrderMarkEnum.valueOfByCharsetName( + csvReadContext.csvReadWorkbookHolder().getCharset().name()); + if (csvReadWorkbookHolder.getMandatoryUseInputStream()) { + return buildCsvParser(csvFormat, csvReadWorkbookHolder.getInputStream(), byteOrderMark); + } + if (csvReadWorkbookHolder.getFile() != null) { + return buildCsvParser(csvFormat, Files.newInputStream(csvReadWorkbookHolder.getFile().toPath()), + byteOrderMark); + } + return buildCsvParser(csvFormat, csvReadWorkbookHolder.getInputStream(), byteOrderMark); + } + + private CSVParser buildCsvParser(CSVFormat csvFormat, InputStream inputStream, ByteOrderMarkEnum byteOrderMark) + throws IOException { + if (byteOrderMark == null) { + return csvFormat.parse( + new InputStreamReader(inputStream, csvReadContext.csvReadWorkbookHolder().getCharset())); + } + return csvFormat.parse(new InputStreamReader(new BOMInputStream(inputStream, byteOrderMark.getByteOrderMark()), + csvReadContext.csvReadWorkbookHolder().getCharset())); + } + + private void dealRecord(CSVRecord record, int rowIndex) { + Map cellMap = new LinkedHashMap<>(); + Iterator cellIterator = record.iterator(); + int columnIndex = 0; + Boolean autoTrim = csvReadContext.currentReadHolder().globalConfiguration().getAutoTrim(); + while (cellIterator.hasNext()) { + String cellString = cellIterator.next(); + ReadCellData readCellData = new ReadCellData<>(); + readCellData.setRowIndex(rowIndex); + readCellData.setColumnIndex(columnIndex); + + // csv is an empty string of whether ,, is read or ,"", + if (StringUtils.isNotBlank(cellString)) { + readCellData.setType(CellDataTypeEnum.STRING); + readCellData.setStringValue(autoTrim ? cellString.trim() : cellString); + } else { + readCellData.setType(CellDataTypeEnum.EMPTY); + } + cellMap.put(columnIndex++, readCellData); + } + + RowTypeEnum rowType = MapUtils.isEmpty(cellMap) ? RowTypeEnum.EMPTY : RowTypeEnum.DATA; + ReadRowHolder readRowHolder = new ReadRowHolder(rowIndex, rowType, + csvReadContext.readWorkbookHolder().getGlobalConfiguration(), cellMap); + csvReadContext.readRowHolder(readRowHolder); + + csvReadContext.csvReadSheetHolder().setCellMap(cellMap); + csvReadContext.csvReadSheetHolder().setRowIndex(rowIndex); + csvReadContext.analysisEventProcessor().endRow(csvReadContext); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/IgnorableXlsRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/IgnorableXlsRecordHandler.java new file mode 100644 index 0000000..499feeb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/IgnorableXlsRecordHandler.java @@ -0,0 +1,8 @@ +package ai.chat2db.excel.analysis.v03; + +/** + * Need to ignore the current handler without reading the current sheet. + * + * @author Jiaju Zhuang + */ +public interface IgnorableXlsRecordHandler extends XlsRecordHandler {} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsListSheetListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsListSheetListener.java new file mode 100644 index 0000000..b2e812a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsListSheetListener.java @@ -0,0 +1,64 @@ +package ai.chat2db.excel.analysis.v03; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.exception.ExcelAnalysisException; +import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; +import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; +import org.apache.poi.hssf.eventusermodel.HSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFRequest; +import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener; +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.analysis.v03.handlers.BofRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.BoundSheetRecordHandler; +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * In some cases, you need to know the number of sheets in advance and only read the file once in advance. + * + * @author Jiaju Zhuang + */ +public class XlsListSheetListener implements HSSFListener { + private final XlsReadContext xlsReadContext; + private static final Map XLS_RECORD_HANDLER_MAP = new HashMap(); + + static { + XLS_RECORD_HANDLER_MAP.put(BOFRecord.sid, new BofRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(BoundSheetRecord.sid, new BoundSheetRecordHandler()); + } + + public XlsListSheetListener(XlsReadContext xlsReadContext) { + this.xlsReadContext = xlsReadContext; + xlsReadContext.xlsReadWorkbookHolder().setNeedReadSheet(Boolean.FALSE); + } + + @Override + public void processRecord(Record record) { + XlsRecordHandler handler = XLS_RECORD_HANDLER_MAP.get(record.getSid()); + if (handler == null) { + return; + } + handler.processRecord(xlsReadContext, record); + } + + public void execute() { + MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this); + HSSFListener formatListener = new FormatTrackingHSSFListener(listener); + HSSFEventFactory factory = new HSSFEventFactory(); + HSSFRequest request = new HSSFRequest(); + EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener = + new EventWorkbookBuilder.SheetRecordCollectingListener(formatListener); + request.addListenerForAllRecords(workbookBuildingListener); + try { + factory.processWorkbookEvents(request, xlsReadContext.xlsReadWorkbookHolder().getPoifsFileSystem()); + } catch (IOException e) { + throw new ExcelAnalysisException(e); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsRecordHandler.java new file mode 100644 index 0000000..2f7f5d8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsRecordHandler.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.analysis.v03; + +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Intercepts handle xls reads. + * + * @author Dan Zheng + */ +public interface XlsRecordHandler { + /** + * Whether to support + * + * @param xlsReadContext + * @param record + * @return + */ + boolean support(XlsReadContext xlsReadContext, Record record); + + /** + * Processing record + * + * @param xlsReadContext + * @param record + */ + void processRecord(XlsReadContext xlsReadContext, Record record); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsSaxAnalyser.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsSaxAnalyser.java new file mode 100644 index 0000000..014bb32 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/XlsSaxAnalyser.java @@ -0,0 +1,176 @@ +package ai.chat2db.excel.analysis.v03; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.analysis.ExcelReadExecutor; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelAnalysisStopException; +import ai.chat2db.excel.exception.ExcelAnalysisStopSheetException; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; +import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; +import org.apache.poi.hssf.eventusermodel.HSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFRequest; +import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener; +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.BlankRecord; +import org.apache.poi.hssf.record.BoolErrRecord; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.HyperlinkRecord; +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.LabelRecord; +import org.apache.poi.hssf.record.LabelSSTRecord; +import org.apache.poi.hssf.record.MergeCellsRecord; +import org.apache.poi.hssf.record.NoteRecord; +import org.apache.poi.hssf.record.NumberRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.RKRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SSTRecord; +import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ai.chat2db.excel.analysis.v03.handlers.BlankRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.BofRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.BoolErrRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.BoundSheetRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.DummyRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.EofRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.FormulaRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.HyperlinkRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.IndexRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.LabelRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.LabelSstRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.MergeCellsRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.NoteRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.NumberRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.ObjRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.RkRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.SstRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.StringRecordHandler; +import ai.chat2db.excel.analysis.v03.handlers.TextObjectRecordHandler; +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * A text extractor for Excel files. + *

+ * Returns the textual content of the file, suitable for indexing by something like Lucene, but not really intended for + * display to the user. + *

+ * + *

+ * To turn an excel file into a CSV or similar, then see the XLS2CSVmra example + *

+ * + * + * @author jipengfei + * @see XLS2CSVmra + */ +@Slf4j +public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(XlsSaxAnalyser.class); + private static final short DUMMY_RECORD_SID = -1; + private final XlsReadContext xlsReadContext; + private static final Map XLS_RECORD_HANDLER_MAP = new HashMap(32); + + static { + XLS_RECORD_HANDLER_MAP.put(BlankRecord.sid, new BlankRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(BOFRecord.sid, new BofRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(BoolErrRecord.sid, new BoolErrRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(BoundSheetRecord.sid, new BoundSheetRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(DUMMY_RECORD_SID, new DummyRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(EOFRecord.sid, new EofRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(FormulaRecord.sid, new FormulaRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(HyperlinkRecord.sid, new HyperlinkRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(IndexRecord.sid, new IndexRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(LabelRecord.sid, new LabelRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(LabelSSTRecord.sid, new LabelSstRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(MergeCellsRecord.sid, new MergeCellsRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(NoteRecord.sid, new NoteRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(NumberRecord.sid, new NumberRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(ObjRecord.sid, new ObjRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(RKRecord.sid, new RkRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(SSTRecord.sid, new SstRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(StringRecord.sid, new StringRecordHandler()); + XLS_RECORD_HANDLER_MAP.put(TextObjectRecord.sid, new TextObjectRecordHandler()); + } + + public XlsSaxAnalyser(XlsReadContext xlsReadContext) { + this.xlsReadContext = xlsReadContext; + } + + @Override + public List sheetList() { + try { + if (xlsReadContext.readWorkbookHolder().getActualSheetDataList() == null) { + new XlsListSheetListener(xlsReadContext).execute(); + } + } catch (ExcelAnalysisStopException e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Custom stop!"); + } + } + return xlsReadContext.readWorkbookHolder().getActualSheetDataList(); + } + + @Override + public void execute() { + XlsReadWorkbookHolder xlsReadWorkbookHolder = xlsReadContext.xlsReadWorkbookHolder(); + MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this); + xlsReadWorkbookHolder.setFormatTrackingHSSFListener(new FormatTrackingHSSFListener(listener)); + EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener = + new EventWorkbookBuilder.SheetRecordCollectingListener( + xlsReadWorkbookHolder.getFormatTrackingHSSFListener()); + xlsReadWorkbookHolder.setHssfWorkbook(workbookBuildingListener.getStubHSSFWorkbook()); + HSSFEventFactory factory = new HSSFEventFactory(); + HSSFRequest request = new HSSFRequest(); + request.addListenerForAllRecords(xlsReadWorkbookHolder.getFormatTrackingHSSFListener()); + try { + factory.processWorkbookEvents(request, xlsReadWorkbookHolder.getPoifsFileSystem()); + } catch (IOException e) { + throw new ExcelAnalysisException(e); + } + + // There are some special xls that do not have the terminator "[EOF]", so an additional + xlsReadContext.analysisEventProcessor().endSheet(xlsReadContext); + } + + @Override + public void processRecord(Record record) { + XlsRecordHandler handler = XLS_RECORD_HANDLER_MAP.get(record.getSid()); + if (handler == null) { + return; + } + boolean ignoreRecord = + (handler instanceof IgnorableXlsRecordHandler) && xlsReadContext.xlsReadWorkbookHolder().getIgnoreRecord(); + if (ignoreRecord) { + // No need to read the current sheet + return; + } + if (!handler.support(xlsReadContext, record)) { + return; + } + + try { + handler.processRecord(xlsReadContext, record); + } catch (ExcelAnalysisStopSheetException e) { + if (log.isDebugEnabled()) { + log.debug("Custom stop!", e); + } + xlsReadContext.xlsReadWorkbookHolder().setIgnoreRecord(Boolean.TRUE); + xlsReadContext.xlsReadWorkbookHolder().setCurrentSheetStopped(Boolean.TRUE); + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/AbstractXlsRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/AbstractXlsRecordHandler.java new file mode 100644 index 0000000..ee6f585 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/AbstractXlsRecordHandler.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.XlsRecordHandler; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Abstract xls record handler + * + * @author Jiaju Zhuang + **/ +public abstract class AbstractXlsRecordHandler implements XlsRecordHandler { + + @Override + public boolean support(XlsReadContext xlsReadContext, Record record) { + return true; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BlankRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BlankRecordHandler.java new file mode 100644 index 0000000..7b58046 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BlankRecordHandler.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.BlankRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class BlankRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + BlankRecord br = (BlankRecord)record; + xlsReadContext.xlsReadSheetHolder().getCellMap().put((int)br.getColumn(), + ReadCellData.newEmptyInstance(br.getRow(), (int)br.getColumn())); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BofRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BofRecordHandler.java new file mode 100644 index 0000000..30ae404 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BofRecordHandler.java @@ -0,0 +1,75 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.exception.ExcelAnalysisStopException; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; +import ai.chat2db.excel.util.SheetUtils; +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class BofRecordHandler extends AbstractXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + BOFRecord br = (BOFRecord) record; + XlsReadWorkbookHolder xlsReadWorkbookHolder = xlsReadContext.xlsReadWorkbookHolder(); + if (br.getType() == BOFRecord.TYPE_WORKBOOK) { + xlsReadWorkbookHolder.setReadSheetIndex(null); + xlsReadWorkbookHolder.setIgnoreRecord(Boolean.FALSE); + return; + } + if (br.getType() != BOFRecord.TYPE_WORKSHEET) { + return; + } + // Init read sheet Data + initReadSheetDataList(xlsReadWorkbookHolder); + Integer readSheetIndex = xlsReadWorkbookHolder.getReadSheetIndex(); + if (readSheetIndex == null) { + readSheetIndex = 0; + xlsReadWorkbookHolder.setReadSheetIndex(readSheetIndex); + } + ReadSheet actualReadSheet = xlsReadWorkbookHolder.getActualSheetDataList().get(readSheetIndex); + assert actualReadSheet != null : "Can't find the sheet."; + // Copy the parameter to the current sheet + ReadSheet readSheet = SheetUtils.match(actualReadSheet, xlsReadContext); + if (readSheet != null) { + xlsReadContext.currentSheet(readSheet); + xlsReadContext.xlsReadWorkbookHolder().setIgnoreRecord(Boolean.FALSE); + } else { + xlsReadContext.xlsReadWorkbookHolder().setIgnoreRecord(Boolean.TRUE); + } + xlsReadContext.xlsReadWorkbookHolder().setCurrentSheetStopped(Boolean.FALSE); + // Go read the next one + xlsReadWorkbookHolder.setReadSheetIndex(xlsReadWorkbookHolder.getReadSheetIndex() + 1); + } + + private void initReadSheetDataList(XlsReadWorkbookHolder xlsReadWorkbookHolder) { + if (xlsReadWorkbookHolder.getActualSheetDataList() != null) { + return; + } + BoundSheetRecord[] boundSheetRecords = + BoundSheetRecord.orderByBofPosition(xlsReadWorkbookHolder.getBoundSheetRecordList()); + List readSheetDataList = new ArrayList(); + for (int i = 0; i < boundSheetRecords.length; i++) { + BoundSheetRecord boundSheetRecord = boundSheetRecords[i]; + ReadSheet readSheet = new ReadSheet(i, boundSheetRecord.getSheetname()); + readSheetDataList.add(readSheet); + } + xlsReadWorkbookHolder.setActualSheetDataList(readSheetDataList); + // Just need to get the list of sheets + if (!xlsReadWorkbookHolder.getNeedReadSheet()) { + throw new ExcelAnalysisStopException("Just need to get the list of sheets."); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoolErrRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoolErrRecordHandler.java new file mode 100644 index 0000000..354f9dd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoolErrRecordHandler.java @@ -0,0 +1,25 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.BoolErrRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class BoolErrRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + BoolErrRecord ber = (BoolErrRecord)record; + xlsReadContext.xlsReadSheetHolder().getCellMap().put((int)ber.getColumn(), + ReadCellData.newInstance(ber.getBooleanValue(), ber.getRow(), (int)ber.getColumn())); + xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoundSheetRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoundSheetRecordHandler.java new file mode 100644 index 0000000..b2c6437 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/BoundSheetRecordHandler.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class BoundSheetRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + BoundSheetRecord bsr = (BoundSheetRecord)record; + xlsReadContext.xlsReadWorkbookHolder().getBoundSheetRecordList().add(bsr); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/DummyRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/DummyRecordHandler.java new file mode 100644 index 0000000..9d1d6ff --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/DummyRecordHandler.java @@ -0,0 +1,44 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.util.LinkedHashMap; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; +import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class DummyRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + XlsReadSheetHolder xlsReadSheetHolder = xlsReadContext.xlsReadSheetHolder(); + if (record instanceof LastCellOfRowDummyRecord) { + // End of this row + LastCellOfRowDummyRecord lcrdr = (LastCellOfRowDummyRecord)record; + xlsReadSheetHolder.setRowIndex(lcrdr.getRow()); + xlsReadContext.readRowHolder(new ReadRowHolder(lcrdr.getRow(), xlsReadSheetHolder.getTempRowType(), + xlsReadContext.readSheetHolder().getGlobalConfiguration(), xlsReadSheetHolder.getCellMap())); + xlsReadContext.analysisEventProcessor().endRow(xlsReadContext); + xlsReadSheetHolder.setCellMap(new LinkedHashMap()); + xlsReadSheetHolder.setTempRowType(RowTypeEnum.EMPTY); + } else if (record instanceof MissingCellDummyRecord) { + MissingCellDummyRecord mcdr = (MissingCellDummyRecord)record; + // https://github.com/alibaba/easyexcel/issues/2236 + // Some abnormal XLS, in the case of data already exist, or there will be a "MissingCellDummyRecord" + // records, so if the existing data, empty data is ignored + xlsReadSheetHolder.getCellMap().putIfAbsent(mcdr.getColumn(), + ReadCellData.newEmptyInstance(mcdr.getRow(), mcdr.getColumn())); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/EofRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/EofRecordHandler.java new file mode 100644 index 0000000..d326ca2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/EofRecordHandler.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.util.LinkedHashMap; + +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import ai.chat2db.excel.util.BooleanUtils; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class EofRecordHandler extends AbstractXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + if (xlsReadContext.readSheetHolder() == null) { + return; + } + + //Represents the current sheet does not need to be read or the user manually stopped reading the sheet. + if (BooleanUtils.isTrue(xlsReadContext.xlsReadWorkbookHolder().getIgnoreRecord())) { + // When the user manually stops reading the sheet, the method to end the sheet needs to be called. + if (BooleanUtils.isTrue(xlsReadContext.xlsReadWorkbookHolder().getCurrentSheetStopped())) { + xlsReadContext.analysisEventProcessor().endSheet(xlsReadContext); + } + return; + } + + // Sometimes tables lack the end record of the last column + if (!xlsReadContext.xlsReadSheetHolder().getCellMap().isEmpty()) { + XlsReadSheetHolder xlsReadSheetHolder = xlsReadContext.xlsReadSheetHolder(); + // Forge a termination data + xlsReadContext.readRowHolder(new ReadRowHolder(xlsReadContext.xlsReadSheetHolder().getRowIndex() + 1, + xlsReadSheetHolder.getTempRowType(), + xlsReadContext.readSheetHolder().getGlobalConfiguration(), xlsReadSheetHolder.getCellMap())); + xlsReadContext.analysisEventProcessor().endRow(xlsReadContext); + xlsReadSheetHolder.setCellMap(new LinkedHashMap()); + xlsReadSheetHolder.setTempRowType(RowTypeEnum.EMPTY); + } + + xlsReadContext.analysisEventProcessor().endSheet(xlsReadContext); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/FormulaRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/FormulaRecordHandler.java new file mode 100644 index 0000000..517a0ff --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/FormulaRecordHandler.java @@ -0,0 +1,89 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.math.BigDecimal; +import java.util.Map; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.constant.BuiltinFormats; +import ai.chat2db.excel.constant.EasyExcelConstants; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.data.FormulaData; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.model.HSSFFormulaParser; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.ss.usermodel.CellType; + +/** + * Record handler + * + * @author Dan Zheng + */ +@Slf4j +public class FormulaRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + private static final String ERROR = "#VALUE!"; + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + FormulaRecord frec = (FormulaRecord)record; + Map cellMap = xlsReadContext.xlsReadSheetHolder().getCellMap(); + ReadCellData tempCellData = new ReadCellData<>(); + tempCellData.setRowIndex(frec.getRow()); + tempCellData.setColumnIndex((int)frec.getColumn()); + CellType cellType = CellType.forInt(frec.getCachedResultType()); + String formulaValue = null; + try { + formulaValue = HSSFFormulaParser.toFormulaString(xlsReadContext.xlsReadWorkbookHolder().getHssfWorkbook(), + frec.getParsedExpression()); + } catch (Exception e) { + log.debug("Get formula value error.", e); + } + FormulaData formulaData = new FormulaData(); + formulaData.setFormulaValue(formulaValue); + tempCellData.setFormulaData(formulaData); + xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA); + switch (cellType) { + case STRING: + // Formula result is a string + // This is stored in the next record + tempCellData.setType(CellDataTypeEnum.STRING); + xlsReadContext.xlsReadSheetHolder().setTempCellData(tempCellData); + break; + case NUMERIC: + tempCellData.setType(CellDataTypeEnum.NUMBER); + tempCellData.setOriginalNumberValue(BigDecimal.valueOf(frec.getValue())); + tempCellData.setNumberValue( + tempCellData.getOriginalNumberValue().round(EasyExcelConstants.EXCEL_MATH_CONTEXT)); + int dataFormat = + xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatIndex(frec); + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex((short)dataFormat); + dataFormatData.setFormat(BuiltinFormats.getBuiltinFormat(dataFormatData.getIndex(), + xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatString(frec), + xlsReadContext.readSheetHolder().getGlobalConfiguration().getLocale())); + tempCellData.setDataFormatData(dataFormatData); + cellMap.put((int)frec.getColumn(), tempCellData); + break; + case ERROR: + tempCellData.setType(CellDataTypeEnum.ERROR); + tempCellData.setStringValue(ERROR); + cellMap.put((int)frec.getColumn(), tempCellData); + break; + case BOOLEAN: + tempCellData.setType(CellDataTypeEnum.BOOLEAN); + tempCellData.setBooleanValue(frec.getCachedBooleanValue()); + cellMap.put((int)frec.getColumn(), tempCellData); + break; + default: + tempCellData.setType(CellDataTypeEnum.EMPTY); + cellMap.put((int)frec.getColumn(), tempCellData); + break; + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/HyperlinkRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/HyperlinkRecordHandler.java new file mode 100644 index 0000000..8f0ae6c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/HyperlinkRecordHandler.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.metadata.CellExtra; +import org.apache.poi.hssf.record.HyperlinkRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class HyperlinkRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public boolean support(XlsReadContext xlsReadContext, Record record) { + return xlsReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.HYPERLINK); + } + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + HyperlinkRecord hr = (HyperlinkRecord)record; + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, hr.getAddress(), hr.getFirstRow(), + hr.getLastRow(), hr.getFirstColumn(), hr.getLastColumn()); + xlsReadContext.xlsReadSheetHolder().setCellExtra(cellExtra); + xlsReadContext.analysisEventProcessor().extra(xlsReadContext); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/IndexRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/IndexRecordHandler.java new file mode 100644 index 0000000..ee3f264 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/IndexRecordHandler.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Jiaju Zhuang + */ +public class IndexRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + if (xlsReadContext.readSheetHolder() == null) { + return; + } + xlsReadContext.readSheetHolder().setApproximateTotalRowNumber(((IndexRecord)record).getLastRowAdd1()); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelRecordHandler.java new file mode 100644 index 0000000..4b37239 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelRecordHandler.java @@ -0,0 +1,28 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.LabelRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class LabelRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + LabelRecord lrec = (LabelRecord)record; + String data = lrec.getValue(); + if (data != null && xlsReadContext.currentReadHolder().globalConfiguration().getAutoTrim()) { + data = data.trim(); + } + xlsReadContext.xlsReadSheetHolder().getCellMap().put((int)lrec.getColumn(), + ReadCellData.newInstance(data, lrec.getRow(), (int)lrec.getColumn())); + xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelSstRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelSstRecordHandler.java new file mode 100644 index 0000000..b89623f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/LabelSstRecordHandler.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.util.Map; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.LabelSSTRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class LabelSstRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + LabelSSTRecord lsrec = (LabelSSTRecord)record; + ReadCache readCache = xlsReadContext.readWorkbookHolder().getReadCache(); + Map cellMap = xlsReadContext.xlsReadSheetHolder().getCellMap(); + if (readCache == null) { + cellMap.put((int)lsrec.getColumn(), ReadCellData.newEmptyInstance(lsrec.getRow(), (int)lsrec.getColumn())); + return; + } + String data = readCache.get(lsrec.getSSTIndex()); + if (data == null) { + cellMap.put((int)lsrec.getColumn(), ReadCellData.newEmptyInstance(lsrec.getRow(), (int)lsrec.getColumn())); + return; + } + if (xlsReadContext.currentReadHolder().globalConfiguration().getAutoTrim()) { + data = data.trim(); + } + cellMap.put((int)lsrec.getColumn(), ReadCellData.newInstance(data, lsrec.getRow(), (int)lsrec.getColumn())); + xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/MergeCellsRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/MergeCellsRecordHandler.java new file mode 100644 index 0000000..670c0bd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/MergeCellsRecordHandler.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.metadata.CellExtra; +import org.apache.poi.hssf.record.MergeCellsRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.ss.util.CellRangeAddress; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class MergeCellsRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public boolean support(XlsReadContext xlsReadContext, Record record) { + return xlsReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.MERGE); + } + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + MergeCellsRecord mcr = (MergeCellsRecord)record; + for (int i = 0; i < mcr.getNumAreas(); i++) { + CellRangeAddress cellRangeAddress = mcr.getAreaAt(i); + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.MERGE, null, cellRangeAddress.getFirstRow(), + cellRangeAddress.getLastRow(), cellRangeAddress.getFirstColumn(), cellRangeAddress.getLastColumn()); + xlsReadContext.xlsReadSheetHolder().setCellExtra(cellExtra); + xlsReadContext.analysisEventProcessor().extra(xlsReadContext); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NoteRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NoteRecordHandler.java new file mode 100644 index 0000000..0bad94a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NoteRecordHandler.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.metadata.CellExtra; +import org.apache.poi.hssf.record.NoteRecord; +import org.apache.poi.hssf.record.Record; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class NoteRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public boolean support(XlsReadContext xlsReadContext, Record record) { + return xlsReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.COMMENT); + } + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + NoteRecord nr = (NoteRecord)record; + String text = xlsReadContext.xlsReadSheetHolder().getObjectCacheMap().get(nr.getShapeId()); + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.COMMENT, text, nr.getRow(), nr.getColumn()); + xlsReadContext.xlsReadSheetHolder().setCellExtra(cellExtra); + xlsReadContext.analysisEventProcessor().extra(xlsReadContext); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NumberRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NumberRecordHandler.java new file mode 100644 index 0000000..c2ab82b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/NumberRecordHandler.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import java.math.BigDecimal; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.constant.BuiltinFormats; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.NumberRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class NumberRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + NumberRecord nr = (NumberRecord)record; + ReadCellData cellData = ReadCellData.newInstanceOriginal(BigDecimal.valueOf(nr.getValue()), nr.getRow(), + (int)nr.getColumn()); + short dataFormat = (short)xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatIndex( + nr); + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex(dataFormat); + dataFormatData.setFormat(BuiltinFormats.getBuiltinFormat(dataFormat, + xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatString(nr), + xlsReadContext.readSheetHolder().getGlobalConfiguration().getLocale())); + cellData.setDataFormatData(dataFormatData); + xlsReadContext.xlsReadSheetHolder().getCellMap().put((int)nr.getColumn(), cellData); + xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/ObjRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/ObjRecordHandler.java new file mode 100644 index 0000000..1c57eda --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/ObjRecordHandler.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SubRecord; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Jiaju Zhuang + */ +public class ObjRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + ObjRecord or = (ObjRecord)record; + for (SubRecord subRecord : or.getSubRecords()) { + if (subRecord instanceof CommonObjectDataSubRecord) { + CommonObjectDataSubRecord codsr = (CommonObjectDataSubRecord)subRecord; + if (CommonObjectDataSubRecord.OBJECT_TYPE_COMMENT == codsr.getObjectType()) { + xlsReadContext.xlsReadSheetHolder().setTempObjectIndex(codsr.getObjectId()); + } + break; + } + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/RkRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/RkRecordHandler.java new file mode 100644 index 0000000..9ae5f71 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/RkRecordHandler.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.context.xls.XlsReadContext; + +import org.apache.poi.hssf.record.RKRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class RkRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + RKRecord re = (RKRecord)record; + xlsReadContext.xlsReadSheetHolder().getCellMap().put((int)re.getColumn(), + ReadCellData.newEmptyInstance(re.getRow(), (int)re.getColumn())); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/SstRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/SstRecordHandler.java new file mode 100644 index 0000000..c5f7247 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/SstRecordHandler.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.cache.XlsCache; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SSTRecord; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class SstRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + xlsReadContext.readWorkbookHolder().setReadCache(new XlsCache((SSTRecord)record)); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/StringRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/StringRecordHandler.java new file mode 100644 index 0000000..40f9a11 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/StringRecordHandler.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.metadata.data.CellData; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.StringRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Dan Zheng + */ +public class StringRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(StringRecordHandler.class); + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + // String for formula + StringRecord srec = (StringRecord)record; + XlsReadSheetHolder xlsReadSheetHolder = xlsReadContext.xlsReadSheetHolder(); + CellData tempCellData = xlsReadSheetHolder.getTempCellData(); + if (tempCellData == null) { + LOGGER.warn("String type formula but no value found."); + return; + } + tempCellData.setStringValue(srec.getString()); + xlsReadSheetHolder.getCellMap().put(tempCellData.getColumnIndex(), tempCellData); + xlsReadSheetHolder.setTempCellData(null); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/TextObjectRecordHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/TextObjectRecordHandler.java new file mode 100644 index 0000000..d8984e1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v03/handlers/TextObjectRecordHandler.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.analysis.v03.handlers; + +import ai.chat2db.excel.analysis.v03.IgnorableXlsRecordHandler; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ai.chat2db.excel.context.xls.XlsReadContext; + +/** + * Record handler + * + * @author Jiaju Zhuang + */ +public class TextObjectRecordHandler extends AbstractXlsRecordHandler implements IgnorableXlsRecordHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(TextObjectRecordHandler.class); + + @Override + public boolean support(XlsReadContext xlsReadContext, Record record) { + return xlsReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.COMMENT); + } + + @Override + public void processRecord(XlsReadContext xlsReadContext, Record record) { + TextObjectRecord tor = (TextObjectRecord)record; + XlsReadSheetHolder xlsReadSheetHolder = xlsReadContext.xlsReadSheetHolder(); + Integer tempObjectIndex = xlsReadSheetHolder.getTempObjectIndex(); + if (tempObjectIndex == null) { + LOGGER.debug("tempObjectIndex is null."); + return; + } + xlsReadSheetHolder.getObjectCacheMap().put(tempObjectIndex, tor.getStr().getString()); + xlsReadSheetHolder.setTempObjectIndex(null); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/XlsxSaxAnalyser.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/XlsxSaxAnalyser.java new file mode 100644 index 0000000..fe1f3fe --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/XlsxSaxAnalyser.java @@ -0,0 +1,292 @@ +package ai.chat2db.excel.analysis.v07; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelAnalysisStopSheetException; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.util.SheetUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.analysis.ExcelReadExecutor; +import ai.chat2db.excel.analysis.v07.handlers.sax.SharedStringsTableHandler; +import ai.chat2db.excel.analysis.v07.handlers.sax.XlsxRowHandler; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.ss.util.CellAddress; +import org.apache.poi.xssf.eventusermodel.XSSFReader; +import org.apache.poi.xssf.model.Comments; +import org.apache.poi.xssf.model.CommentsTable; +import org.apache.poi.xssf.usermodel.XSSFComment; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbookPr; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.WorkbookDocument; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * @author jipengfei + */ +@Slf4j +public class XlsxSaxAnalyser implements ExcelReadExecutor { + + /** + * Storage sheet SharedStrings + */ + public static final PackagePartName SHARED_STRINGS_PART_NAME; + + static { + try { + SHARED_STRINGS_PART_NAME = PackagingURIHelper.createPartName("/xl/sharedStrings.xml"); + } catch (InvalidFormatException e) { + log.error("Initialize the XlsxSaxAnalyser failure", e); + throw new ExcelAnalysisException("Initialize the XlsxSaxAnalyser failure", e); + } + } + + private final XlsxReadContext xlsxReadContext; + private final List sheetList; + private final Map sheetMap; + /** + * excel comments key: sheetNo value: CommentsTable + */ + private final Map commentsTableMap; + + public XlsxSaxAnalyser(XlsxReadContext xlsxReadContext, InputStream decryptedStream) throws Exception { + this.xlsxReadContext = xlsxReadContext; + // Initialize cache + XlsxReadWorkbookHolder xlsxReadWorkbookHolder = xlsxReadContext.xlsxReadWorkbookHolder(); + + OPCPackage pkg = readOpcPackage(xlsxReadWorkbookHolder, decryptedStream); + xlsxReadWorkbookHolder.setOpcPackage(pkg); + + // Read the Shared information Strings + PackagePart sharedStringsTablePackagePart = pkg.getPart(SHARED_STRINGS_PART_NAME); + if (sharedStringsTablePackagePart != null) { + // Specify default cache + defaultReadCache(xlsxReadWorkbookHolder, sharedStringsTablePackagePart); + + // Analysis sharedStringsTable.xml + analysisSharedStringsTable(sharedStringsTablePackagePart.getInputStream(), xlsxReadWorkbookHolder); + } + + XSSFReader xssfReader = new XSSFReader(pkg); + analysisUse1904WindowDate(xssfReader, xlsxReadWorkbookHolder); + + // set style table + setStylesTable(xlsxReadWorkbookHolder, xssfReader); + + sheetList = new ArrayList<>(); + sheetMap = new HashMap<>(); + commentsTableMap = new HashMap<>(); + Map packageRelationshipCollectionMap = MapUtils.newHashMap(); + xlsxReadWorkbookHolder.setPackageRelationshipCollectionMap(packageRelationshipCollectionMap); + + XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData(); + int index = 0; + if (!ite.hasNext()) { + throw new ExcelAnalysisException("Can not find any sheet!"); + } + while (ite.hasNext()) { + InputStream inputStream = ite.next(); + sheetList.add(new ReadSheet(index, ite.getSheetName())); + sheetMap.put(index, inputStream); + if (xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.COMMENT)) { + Comments comments = ite.getSheetComments(); + if (comments instanceof CommentsTable) { + commentsTableMap.put(index, (CommentsTable) comments); + } + } + if (xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.HYPERLINK)) { + PackageRelationshipCollection packageRelationshipCollection = Optional.ofNullable(ite.getSheetPart()) + .map(packagePart -> { + try { + return packagePart.getRelationships(); + } catch (InvalidFormatException e) { + log.warn("Reading the Relationship failed", e); + return null; + } + }).orElse(null); + if (packageRelationshipCollection != null) { + packageRelationshipCollectionMap.put(index, packageRelationshipCollection); + } + } + index++; + } + } + + private void setStylesTable(XlsxReadWorkbookHolder xlsxReadWorkbookHolder, XSSFReader xssfReader) { + try { + xlsxReadWorkbookHolder.setStylesTable(xssfReader.getStylesTable()); + } catch (Exception e) { + log.warn( + "Currently excel cannot get style information, but it doesn't affect the data analysis.You can try to" + + " save the file with office again or ignore the current error.", + e); + } + } + + private void defaultReadCache(XlsxReadWorkbookHolder xlsxReadWorkbookHolder, + PackagePart sharedStringsTablePackagePart) { + ReadCache readCache = xlsxReadWorkbookHolder.getReadCacheSelector().readCache(sharedStringsTablePackagePart); + xlsxReadWorkbookHolder.setReadCache(readCache); + readCache.init(xlsxReadContext); + } + + private void analysisUse1904WindowDate(XSSFReader xssfReader, XlsxReadWorkbookHolder xlsxReadWorkbookHolder) + throws Exception { + if (xlsxReadWorkbookHolder.globalConfiguration().getUse1904windowing() != null) { + return; + } + InputStream workbookXml = xssfReader.getWorkbookData(); + WorkbookDocument ctWorkbook = WorkbookDocument.Factory.parse(workbookXml); + CTWorkbook wb = ctWorkbook.getWorkbook(); + CTWorkbookPr prefix = wb.getWorkbookPr(); + if (prefix != null && prefix.getDate1904()) { + xlsxReadWorkbookHolder.getGlobalConfiguration().setUse1904windowing(Boolean.TRUE); + } else { + xlsxReadWorkbookHolder.getGlobalConfiguration().setUse1904windowing(Boolean.FALSE); + } + } + + private void analysisSharedStringsTable(InputStream sharedStringsTableInputStream, + XlsxReadWorkbookHolder xlsxReadWorkbookHolder) { + ContentHandler handler = new SharedStringsTableHandler(xlsxReadWorkbookHolder.getReadCache()); + parseXmlSource(sharedStringsTableInputStream, handler); + xlsxReadWorkbookHolder.getReadCache().putFinished(); + } + + private OPCPackage readOpcPackage(XlsxReadWorkbookHolder xlsxReadWorkbookHolder, InputStream decryptedStream) + throws Exception { + if (decryptedStream == null && xlsxReadWorkbookHolder.getFile() != null) { + return OPCPackage.open(xlsxReadWorkbookHolder.getFile()); + } + if (xlsxReadWorkbookHolder.getMandatoryUseInputStream()) { + if (decryptedStream != null) { + return OPCPackage.open(decryptedStream); + } else { + return OPCPackage.open(xlsxReadWorkbookHolder.getInputStream()); + } + } + File readTempFile = FileUtils.createCacheTmpFile(); + xlsxReadWorkbookHolder.setTempFile(readTempFile); + File tempFile = new File(readTempFile.getPath(), UUID.randomUUID() + ".xlsx"); + if (decryptedStream != null) { + FileUtils.writeToFile(tempFile, decryptedStream, false); + } else { + FileUtils.writeToFile(tempFile, xlsxReadWorkbookHolder.getInputStream(), + xlsxReadWorkbookHolder.getAutoCloseStream()); + } + return OPCPackage.open(tempFile, PackageAccess.READ); + } + + @Override + public List sheetList() { + return sheetList; + } + + private void parseXmlSource(InputStream inputStream, ContentHandler handler) { + InputSource inputSource = new InputSource(inputStream); + try { + SAXParserFactory saxFactory; + String xlsxSAXParserFactoryName = xlsxReadContext.xlsxReadWorkbookHolder().getSaxParserFactoryName(); + if (StringUtils.isEmpty(xlsxSAXParserFactoryName)) { + saxFactory = SAXParserFactory.newInstance(); + } else { + saxFactory = SAXParserFactory.newInstance(xlsxSAXParserFactoryName, null); + } + try { + saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (Throwable ignore) {} + try { + saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + } catch (Throwable ignore) {} + try { + saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (Throwable ignore) {} + SAXParser saxParser = saxFactory.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.setContentHandler(handler); + xmlReader.parse(inputSource); + inputStream.close(); + } catch (IOException | ParserConfigurationException | SAXException e) { + throw new ExcelAnalysisException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new ExcelAnalysisException("Can not close 'inputStream'!"); + } + } + } + } + + @Override + public void execute() { + for (ReadSheet readSheet : sheetList) { + readSheet = SheetUtils.match(readSheet, xlsxReadContext); + if (readSheet != null) { + try { + xlsxReadContext.currentSheet(readSheet); + parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(xlsxReadContext)); + // Read comments + readComments(readSheet); + } catch (ExcelAnalysisStopSheetException e) { + if (log.isDebugEnabled()) { + log.debug("Custom stop!", e); + } + } + // The last sheet is read + xlsxReadContext.analysisEventProcessor().endSheet(xlsxReadContext); + } + } + } + + private void readComments(ReadSheet readSheet) { + if (!xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.COMMENT)) { + return; + } + CommentsTable commentsTable = commentsTableMap.get(readSheet.getSheetNo()); + if (commentsTable == null) { + return; + } + Iterator cellAddresses = commentsTable.getCellAddresses(); + while (cellAddresses.hasNext()) { + CellAddress cellAddress = cellAddresses.next(); + XSSFComment cellComment = commentsTable.findCellComment(cellAddress); + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.COMMENT, cellComment.getString().toString(), + cellAddress.getRow(), cellAddress.getColumn()); + xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); + xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractCellValueTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractCellValueTagHandler.java new file mode 100644 index 0000000..828b59a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractCellValueTagHandler.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +/** + * Cell Value Handler + * + * @author jipengfei + */ +public abstract class AbstractCellValueTagHandler extends AbstractXlsxTagHandler { + + @Override + public void characters(XlsxReadContext xlsxReadContext, char[] ch, int start, int length) { + xlsxReadContext.xlsxReadSheetHolder().getTempData().append(ch, start, length); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractXlsxTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractXlsxTagHandler.java new file mode 100644 index 0000000..956325d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/AbstractXlsxTagHandler.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import org.xml.sax.Attributes; + +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +/** + * Abstract tag handler + * + * @author Jiaju Zhuang + */ +public abstract class AbstractXlsxTagHandler implements XlsxTagHandler { + @Override + public boolean support(XlsxReadContext xlsxReadContext) { + return true; + } + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + + } + + @Override + public void endElement(XlsxReadContext xlsxReadContext, String name) { + + } + + @Override + public void characters(XlsxReadContext xlsxReadContext, char[] ch, int start, int length) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellFormulaTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellFormulaTagHandler.java new file mode 100644 index 0000000..5624cb9 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellFormulaTagHandler.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import ai.chat2db.excel.metadata.data.FormulaData; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import org.xml.sax.Attributes; + +/** + * Cell Handler + * + * @author jipengfei + */ +public class CellFormulaTagHandler extends AbstractXlsxTagHandler { + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + xlsxReadSheetHolder.setTempFormula(new StringBuilder()); + } + + @Override + public void endElement(XlsxReadContext xlsxReadContext, String name) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + FormulaData formulaData = new FormulaData(); + formulaData.setFormulaValue(xlsxReadSheetHolder.getTempFormula().toString()); + xlsxReadSheetHolder.getTempCellData().setFormulaData(formulaData); + } + + @Override + public void characters(XlsxReadContext xlsxReadContext, char[] ch, int start, int length) { + xlsxReadContext.xlsxReadSheetHolder().getTempFormula().append(ch, start, length); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellInlineStringValueTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellInlineStringValueTagHandler.java new file mode 100644 index 0000000..f15b31b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellInlineStringValueTagHandler.java @@ -0,0 +1,10 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +/** + * Cell inline string value handler + * + * @author jipengfei + */ +public class CellInlineStringValueTagHandler extends AbstractCellValueTagHandler { + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellTagHandler.java new file mode 100644 index 0000000..0a70a1c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellTagHandler.java @@ -0,0 +1,110 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import java.math.BigDecimal; + +import ai.chat2db.excel.constant.EasyExcelConstants; +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.util.PositionUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import org.xml.sax.Attributes; + +/** + * Cell Handler + * + * @author jipengfei + */ +public class CellTagHandler extends AbstractXlsxTagHandler { + + private static final int DEFAULT_FORMAT_INDEX = 0; + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + xlsxReadSheetHolder.setColumnIndex(PositionUtils.getCol(attributes.getValue(ExcelXmlConstants.ATTRIBUTE_R), + xlsxReadSheetHolder.getColumnIndex())); + + // t="s" ,it means String + // t="str" ,it means String,but does not need to be read in the 'sharedStrings.xml' + // t="inlineStr" ,it means String,but does not need to be read in the 'sharedStrings.xml' + // t="b" ,it means Boolean + // t="e" ,it means Error + // t="n" ,it means Number + // t is null ,it means Empty or Number + CellDataTypeEnum type = CellDataTypeEnum.buildFromCellType(attributes.getValue(ExcelXmlConstants.ATTRIBUTE_T)); + xlsxReadSheetHolder.setTempCellData(new ReadCellData<>(type)); + xlsxReadSheetHolder.setTempData(new StringBuilder()); + + // Put in data transformation information + String dateFormatIndex = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_S); + int dateFormatIndexInteger; + if (StringUtils.isEmpty(dateFormatIndex)) { + dateFormatIndexInteger = DEFAULT_FORMAT_INDEX; + } else { + dateFormatIndexInteger = Integer.parseInt(dateFormatIndex); + } + + xlsxReadSheetHolder.getTempCellData().setDataFormatData( + xlsxReadContext.xlsxReadWorkbookHolder().dataFormatData(dateFormatIndexInteger)); + } + + @Override + public void endElement(XlsxReadContext xlsxReadContext, String name) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + ReadCellData tempCellData = xlsxReadSheetHolder.getTempCellData(); + StringBuilder tempData = xlsxReadSheetHolder.getTempData(); + String tempDataString = tempData.toString(); + CellDataTypeEnum oldType = tempCellData.getType(); + switch (oldType) { + case STRING: + // In some cases, although cell type is a string, it may be an empty tag + if (StringUtils.isEmpty(tempDataString)) { + break; + } + String stringValue = xlsxReadContext.readWorkbookHolder().getReadCache().get( + Integer.valueOf(tempDataString)); + tempCellData.setStringValue(stringValue); + break; + case DIRECT_STRING: + case ERROR: + tempCellData.setStringValue(tempDataString); + tempCellData.setType(CellDataTypeEnum.STRING); + break; + case BOOLEAN: + if (StringUtils.isEmpty(tempDataString)) { + tempCellData.setType(CellDataTypeEnum.EMPTY); + break; + } + tempCellData.setBooleanValue(BooleanUtils.valueOf(tempData.toString())); + break; + case NUMBER: + case EMPTY: + if (StringUtils.isEmpty(tempDataString)) { + tempCellData.setType(CellDataTypeEnum.EMPTY); + break; + } + tempCellData.setType(CellDataTypeEnum.NUMBER); + tempCellData.setOriginalNumberValue(new BigDecimal(tempDataString)); + tempCellData.setNumberValue( + tempCellData.getOriginalNumberValue().round(EasyExcelConstants.EXCEL_MATH_CONTEXT)); + break; + default: + throw new IllegalStateException("Cannot set values now"); + } + + if (tempCellData.getStringValue() != null + && xlsxReadContext.currentReadHolder().globalConfiguration().getAutoTrim()) { + tempCellData.setStringValue(tempCellData.getStringValue().trim()); + } + + tempCellData.checkEmpty(); + tempCellData.setRowIndex(xlsxReadSheetHolder.getRowIndex()); + tempCellData.setColumnIndex(xlsxReadSheetHolder.getColumnIndex()); + xlsxReadSheetHolder.getCellMap().put(xlsxReadSheetHolder.getColumnIndex(), tempCellData); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellValueTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellValueTagHandler.java new file mode 100644 index 0000000..e3edf89 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CellValueTagHandler.java @@ -0,0 +1,10 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +/** + * Cell Value Handler + * + * @author jipengfei + */ +public class CellValueTagHandler extends AbstractCellValueTagHandler { + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CountTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CountTagHandler.java new file mode 100644 index 0000000..96547e5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/CountTagHandler.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.util.PositionUtils; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import org.xml.sax.Attributes; + +/** + * Cell Handler + * + * @author jipengfei + */ +public class CountTagHandler extends AbstractXlsxTagHandler { + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + String d = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_REF); + String totalStr = d.substring(d.indexOf(":") + 1); + xlsxReadContext.readSheetHolder().setApproximateTotalRowNumber(PositionUtils.getRow(totalStr) + 1); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/HyperlinkTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/HyperlinkTagHandler.java new file mode 100644 index 0000000..dfe6256 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/HyperlinkTagHandler.java @@ -0,0 +1,58 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import java.util.Optional; + +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.util.StringUtils; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.xml.sax.Attributes; + +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +/** + * Cell Handler + * + * @author Jiaju Zhuang + */ +public class HyperlinkTagHandler extends AbstractXlsxTagHandler { + + @Override + public boolean support(XlsxReadContext xlsxReadContext) { + return xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.HYPERLINK); + } + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + String ref = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_REF); + if (StringUtils.isEmpty(ref)) { + return; + } + // Hyperlink has 2 case: + // case 1,In the 'location' tag + String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION); + if (location != null) { + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, location, ref); + xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); + xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); + return; + } + // case 2, In the 'r:id' tag, Then go to 'PackageRelationshipCollection' to get inside + String rId = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_RID); + PackageRelationshipCollection packageRelationshipCollection = xlsxReadContext.xlsxReadSheetHolder() + .getPackageRelationshipCollection(); + if (rId == null || packageRelationshipCollection == null) { + return; + } + Optional.ofNullable(packageRelationshipCollection.getRelationshipByID(rId)) + .map(PackageRelationship::getTargetURI) + .ifPresent(uri -> { + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, uri.toString(), ref); + xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); + xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); + }); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/MergeCellTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/MergeCellTagHandler.java new file mode 100644 index 0000000..02320e4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/MergeCellTagHandler.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.util.StringUtils; +import org.xml.sax.Attributes; + +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +/** + * Cell Handler + * + * @author Jiaju Zhuang + */ +public class MergeCellTagHandler extends AbstractXlsxTagHandler { + + @Override + public boolean support(XlsxReadContext xlsxReadContext) { + return xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.MERGE); + } + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + String ref = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_REF); + if (StringUtils.isEmpty(ref)) { + return; + } + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.MERGE, null, ref); + xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); + xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/RowTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/RowTagHandler.java new file mode 100644 index 0000000..78f2b90 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/RowTagHandler.java @@ -0,0 +1,71 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import java.util.LinkedHashMap; + +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.util.PositionUtils; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import org.apache.commons.collections4.MapUtils; +import org.xml.sax.Attributes; + +/** + * Cell Handler + * + * @author jipengfei + */ +public class RowTagHandler extends AbstractXlsxTagHandler { + + @Override + public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + int rowIndex = PositionUtils.getRowByRowTagt(attributes.getValue(ExcelXmlConstants.ATTRIBUTE_R), + xlsxReadSheetHolder.getRowIndex()); + Integer lastRowIndex = xlsxReadContext.readSheetHolder().getRowIndex(); + while (lastRowIndex + 1 < rowIndex) { + xlsxReadContext.readRowHolder(new ReadRowHolder(lastRowIndex + 1, RowTypeEnum.EMPTY, + xlsxReadSheetHolder.getGlobalConfiguration(), new LinkedHashMap())); + xlsxReadContext.analysisEventProcessor().endRow(xlsxReadContext); + xlsxReadSheetHolder.setColumnIndex(null); + xlsxReadSheetHolder.setCellMap(new LinkedHashMap()); + lastRowIndex++; + } + xlsxReadSheetHolder.setRowIndex(rowIndex); + } + + @Override + public void endElement(XlsxReadContext xlsxReadContext, String name) { + XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder(); + RowTypeEnum rowType = MapUtils.isEmpty(xlsxReadSheetHolder.getCellMap()) ? RowTypeEnum.EMPTY : RowTypeEnum.DATA; + // It's possible that all of the cells in the row are empty + if (rowType == RowTypeEnum.DATA) { + boolean hasData = false; + for (Cell cell : xlsxReadSheetHolder.getCellMap().values()) { + if (!(cell instanceof ReadCellData)) { + hasData = true; + break; + } + ReadCellData readCellData = (ReadCellData)cell; + if (readCellData.getType() != CellDataTypeEnum.EMPTY) { + hasData = true; + break; + } + } + if (!hasData) { + rowType = RowTypeEnum.EMPTY; + } + } + xlsxReadContext.readRowHolder(new ReadRowHolder(xlsxReadSheetHolder.getRowIndex(), rowType, + xlsxReadSheetHolder.getGlobalConfiguration(), xlsxReadSheetHolder.getCellMap())); + xlsxReadContext.analysisEventProcessor().endRow(xlsxReadContext); + xlsxReadSheetHolder.setColumnIndex(null); + xlsxReadSheetHolder.setCellMap(new LinkedHashMap<>()); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/XlsxTagHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/XlsxTagHandler.java new file mode 100644 index 0000000..ce5ebe3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/XlsxTagHandler.java @@ -0,0 +1,54 @@ +package ai.chat2db.excel.analysis.v07.handlers; + +import org.xml.sax.Attributes; + +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +/** + * Tag handler + * + * @author Dan Zheng + */ +public interface XlsxTagHandler { + + /** + * Whether to support + * + * @param xlsxReadContext + * @return + */ + boolean support(XlsxReadContext xlsxReadContext); + + /** + * Start handle + * + * @param xlsxReadContext + * xlsxReadContext + * @param name + * Tag name + * @param attributes + * Tag attributes + */ + void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes); + + /** + * End handle + * + * @param xlsxReadContext + * xlsxReadContext + * @param name + * Tag name + */ + void endElement(XlsxReadContext xlsxReadContext, String name); + + /** + * Read data + * + * @param xlsxReadContext + * @param ch + * @param start + * @param length + */ + void characters(XlsxReadContext xlsxReadContext, char[] ch, int start, int length); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java new file mode 100644 index 0000000..a57a047 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java @@ -0,0 +1,181 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package ai.chat2db.excel.analysis.v07.handlers.sax; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.constant.ExcelXmlConstants; + +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Sax read sharedStringsTable.xml + * + * @author Jiaju Zhuang + */ +public class SharedStringsTableHandler extends DefaultHandler { + + private static final Pattern UTF_PATTTERN = Pattern.compile("_x([0-9A-Fa-f]{4})_"); + + /** + * The final piece of data + */ + private StringBuilder currentData; + /** + * Current element data + */ + private StringBuilder currentElementData; + + private final ReadCache readCache; + /** + * Some fields in the T tag need to be ignored + */ + private boolean ignoreTagt = false; + /** + * The only time you need to read the characters in the T tag is when it is used + */ + private boolean isTagt = false; + + public SharedStringsTableHandler(ReadCache readCache) { + this.readCache = readCache; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) { + if (name == null) { + return; + } + switch (name) { + case ExcelXmlConstants.SHAREDSTRINGS_T_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_T_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_T_TAG: + currentElementData = null; + isTagt = true; + break; + case ExcelXmlConstants.SHAREDSTRINGS_SI_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_SI_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_SI_TAG: + currentData = null; + break; + case ExcelXmlConstants.SHAREDSTRINGS_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_RPH_TAG: + ignoreTagt = true; + break; + default: + // ignore + } + } + + @Override + public void endElement(String uri, String localName, String name) { + if (name == null) { + return; + } + switch (name) { + case ExcelXmlConstants.SHAREDSTRINGS_T_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_T_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_T_TAG: + if (currentElementData != null) { + if (currentData == null) { + currentData = new StringBuilder(); + } + currentData.append(currentElementData); + } + isTagt = false; + break; + case ExcelXmlConstants.SHAREDSTRINGS_SI_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_SI_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_SI_TAG: + if (currentData == null) { + readCache.put(null); + } else { + readCache.put(utfDecode(currentData.toString())); + } + break; + case ExcelXmlConstants.SHAREDSTRINGS_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_RPH_TAG: + ignoreTagt = false; + break; + default: + // ignore + } + } + + @Override + public void characters(char[] ch, int start, int length) { + if (!isTagt || ignoreTagt) { + return; + } + if (currentElementData == null) { + currentElementData = new StringBuilder(); + } + currentElementData.append(ch, start, length); + } + + /** + * from poi XSSFRichTextString + * + * @param value the string to decode + * @return the decoded string or null if the input string is null + *

+ * For all characters which cannot be represented in XML as defined by the XML 1.0 specification, + * the characters are escaped using the Unicode numerical character representation escape character + * format _xHHHH_, where H represents a hexadecimal character in the character's value. + *

+ * Example: The Unicode character 0D is invalid in an XML 1.0 document, + * so it shall be escaped as _x000D_. + *

+ * See section 3.18.9 in the OOXML spec. + * @see org.apache.poi.xssf.usermodel.XSSFRichTextString#utfDecode(String) + */ + static String utfDecode(String value) { + if (value == null || !value.contains("_x")) { + return value; + } + + StringBuilder buf = new StringBuilder(); + Matcher m = UTF_PATTTERN.matcher(value); + int idx = 0; + while (m.find()) { + int pos = m.start(); + if (pos > idx) { + buf.append(value, idx, pos); + } + + String code = m.group(1); + int icode = Integer.decode("0x" + code); + buf.append((char)icode); + + idx = m.end(); + } + + // small optimization: don't go via StringBuilder if not necessary, + // the encodings are very rare, so we should almost always go via this shortcut. + if (idx == 0) { + return value; + } + + buf.append(value.substring(idx)); + return buf.toString(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/XlsxRowHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/XlsxRowHandler.java new file mode 100644 index 0000000..c7cbd4d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/analysis/v07/handlers/sax/XlsxRowHandler.java @@ -0,0 +1,103 @@ +package ai.chat2db.excel.analysis.v07.handlers.sax; + +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.constant.ExcelXmlConstants; +import ai.chat2db.excel.analysis.v07.handlers.CellFormulaTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.CellInlineStringValueTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.CellTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.CellValueTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.CountTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.HyperlinkTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.MergeCellTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.RowTagHandler; +import ai.chat2db.excel.analysis.v07.handlers.XlsxTagHandler; +import ai.chat2db.excel.context.xlsx.XlsxReadContext; + +import lombok.extern.slf4j.Slf4j; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * @author jipengfei + */ +@Slf4j +public class XlsxRowHandler extends DefaultHandler { + private final XlsxReadContext xlsxReadContext; + private static final Map XLSX_CELL_HANDLER_MAP = new HashMap<>(64); + + static { + CellFormulaTagHandler cellFormulaTagHandler = new CellFormulaTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_FORMULA_TAG, cellFormulaTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_FORMULA_TAG, cellFormulaTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_FORMULA_TAG, cellFormulaTagHandler); + CellInlineStringValueTagHandler cellInlineStringValueTagHandler = new CellInlineStringValueTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler); + CellTagHandler cellTagHandler = new CellTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_TAG, cellTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_TAG, cellTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_TAG, cellTagHandler); + CellValueTagHandler cellValueTagHandler = new CellValueTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_VALUE_TAG, cellValueTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_VALUE_TAG, cellValueTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_VALUE_TAG, cellValueTagHandler); + CountTagHandler countTagHandler = new CountTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.DIMENSION_TAG, countTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_DIMENSION_TAG, countTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_DIMENSION_TAG, countTagHandler); + HyperlinkTagHandler hyperlinkTagHandler = new HyperlinkTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.HYPERLINK_TAG, hyperlinkTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_HYPERLINK_TAG, hyperlinkTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_HYPERLINK_TAG, hyperlinkTagHandler); + MergeCellTagHandler mergeCellTagHandler = new MergeCellTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.MERGE_CELL_TAG, mergeCellTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_MERGE_CELL_TAG, mergeCellTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_MERGE_CELL_TAG, mergeCellTagHandler); + RowTagHandler rowTagHandler = new RowTagHandler(); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.ROW_TAG, rowTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_ROW_TAG, rowTagHandler); + XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_ROW_TAG, rowTagHandler); + } + + public XlsxRowHandler(XlsxReadContext xlsxReadContext) { + this.xlsxReadContext = xlsxReadContext; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { + XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(name); + if (handler == null || !handler.support(xlsxReadContext)) { + return; + } + xlsxReadContext.xlsxReadSheetHolder().getTagDeque().push(name); + handler.startElement(xlsxReadContext, name, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + String currentTag = xlsxReadContext.xlsxReadSheetHolder().getTagDeque().peek(); + if (currentTag == null) { + return; + } + XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(currentTag); + if (handler == null || !handler.support(xlsxReadContext)) { + return; + } + handler.characters(xlsxReadContext, ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + XlsxTagHandler handler = XLSX_CELL_HANDLER_MAP.get(name); + if (handler == null || !handler.support(xlsxReadContext)) { + return; + } + handler.endElement(xlsxReadContext, name); + xlsxReadContext.xlsxReadSheetHolder().getTagDeque().pop(); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnore.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnore.java new file mode 100644 index 0000000..00180e7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnore.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Ignore convert excel + * + * @author Jiaju Zhuang + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelIgnore {} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnoreUnannotated.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnoreUnannotated.java new file mode 100644 index 0000000..fd519a1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelIgnoreUnannotated.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Ignore all unannotated fields. + * + * @author Jiaju Zhuang + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelIgnoreUnannotated { +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelProperty.java new file mode 100644 index 0000000..0b69916 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/ExcelProperty.java @@ -0,0 +1,69 @@ +package ai.chat2db.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.converters.AutoConverter; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.annotation.format.DateTimeFormat; + +/** + * @author jipengfei + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelProperty { + + /** + * The name of the sheet header. + * + *

+ * write: It automatically merges when you have more than one head + *

+ * read: When you have multiple heads, take the last one + * + * @return The name of the sheet header + */ + String[] value() default {""}; + + /** + * Index of column + * + * Read or write it on the index of column, If it's equal to -1, it's sorted by Java class. + * + * priority: index > order > default sort + * + * @return Index of column + */ + int index() default -1; + + /** + * Defines the sort order for an column. + * + * priority: index > order > default sort + * + * @return Order of column + */ + int order() default Integer.MAX_VALUE; + + /** + * Force the current field to use this converter. + * + * @return Converter + */ + Class> converter() default AutoConverter.class; + + /** + * + * default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format + * + * @return Format string + * @deprecated please use {@link DateTimeFormat} + */ + @Deprecated + String format() default ""; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/DateTimeFormat.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/DateTimeFormat.java new file mode 100644 index 0000000..0b87b49 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/DateTimeFormat.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.annotation.format; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.enums.BooleanEnum; + +/** + * Convert date format. + * + *

+ * write: It can be used on classes {@link java.util.Date} + *

+ * read: It can be used on classes {@link String} + * + * @author Jiaju Zhuang + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DateTimeFormat { + + /** + * + * Specific format reference {@link java.text.SimpleDateFormat} + * + * @return Format pattern + */ + String value() default ""; + + /** + * True if date uses 1904 windowing, or false if using 1900 date windowing. + * + * @return True if date uses 1904 windowing, or false if using 1900 date windowing. + */ + BooleanEnum use1904windowing() default BooleanEnum.DEFAULT; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/NumberFormat.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/NumberFormat.java new file mode 100644 index 0000000..ca7675f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/format/NumberFormat.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.annotation.format; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.RoundingMode; + +/** + * Convert number format. + * + *

+ * write: It can be used on classes that inherit {@link Number} + *

+ * read: It can be used on classes {@link String} + * + * @author Jiaju Zhuang + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface NumberFormat { + + /** + * + * Specific format reference {@link java.text.DecimalFormat} + * + * @return Format pattern + */ + String value() default ""; + + /** + * Rounded by default + * + * @return RoundingMode + */ + RoundingMode roundingMode() default RoundingMode.HALF_UP; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ColumnWidth.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ColumnWidth.java new file mode 100644 index 0000000..b228a21 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ColumnWidth.java @@ -0,0 +1,27 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Set the width of the table + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ColumnWidth { + + /** + * Column width + *

+ * -1 means the default column width is used + * + * @return Column width + */ + int value() default -1; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentFontStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentFontStyle.java new file mode 100644 index 0000000..ba868b5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentFontStyle.java @@ -0,0 +1,91 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.enums.BooleanEnum; + +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hssf.usermodel.HSSFPalette; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Custom content styles. + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ContentFontStyle { + + /** + * The name for the font (i.e. Arial) + */ + String fontName() default ""; + + /** + * Height in the familiar unit of measure - points + */ + short fontHeightInPoints() default -1; + + /** + * Whether to use italics or not + */ + BooleanEnum italic() default BooleanEnum.DEFAULT; + + /** + * Whether to use a strikeout horizontal line through the text or not + */ + BooleanEnum strikeout() default BooleanEnum.DEFAULT; + + /** + * The color for the font + * + * @see Font#COLOR_NORMAL + * @see Font#COLOR_RED + * @see HSSFPalette#getColor(short) + * @see IndexedColors + */ + short color() default -1; + + /** + * Set normal, super or subscript. + * + * @see Font#SS_NONE + * @see Font#SS_SUPER + * @see Font#SS_SUB + */ + short typeOffset() default -1; + + /** + * set type of text underlining to use + * + * @see Font#U_NONE + * @see Font#U_SINGLE + * @see Font#U_DOUBLE + * @see Font#U_SINGLE_ACCOUNTING + * @see Font#U_DOUBLE_ACCOUNTING + */ + + byte underline() default -1; + + /** + * Set character-set to use. + * + * @see FontCharset + * @see Font#ANSI_CHARSET + * @see Font#DEFAULT_CHARSET + * @see Font#SYMBOL_CHARSET + */ + int charset() default -1; + + /** + * Bold + */ + BooleanEnum bold() default BooleanEnum.DEFAULT; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentLoopMerge.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentLoopMerge.java new file mode 100644 index 0000000..f209fdf --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentLoopMerge.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The regions of the loop merge + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ContentLoopMerge { + /** + * Each row + * + * @return + */ + int eachRow() default 1; + + /** + * Extend column + * + * @return + */ + int columnExtend() default 1; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentRowHeight.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentRowHeight.java new file mode 100644 index 0000000..0ab51d0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentRowHeight.java @@ -0,0 +1,27 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Set the height of each table + * + * @author Jiaju Zhuang + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ContentRowHeight { + + /** + * Set the content height + *

+ * -1 mean the auto set height + * + * @return Content height + */ + short value() default -1; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentStyle.java new file mode 100644 index 0000000..95121b0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/ContentStyle.java @@ -0,0 +1,162 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.enums.BooleanEnum; +import ai.chat2db.excel.enums.poi.BorderStyleEnum; +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; +import ai.chat2db.excel.enums.poi.HorizontalAlignmentEnum; +import ai.chat2db.excel.enums.poi.VerticalAlignmentEnum; + +import org.apache.poi.ss.usermodel.BuiltinFormats; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IgnoredErrorType; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Custom content styles + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ContentStyle { + /** + * Set the data format (must be a valid format). Built in formats are defined at {@link BuiltinFormats}. + */ + short dataFormat() default -1; + + /** + * Set the cell's using this style to be hidden + */ + BooleanEnum hidden() default BooleanEnum.DEFAULT; + + /** + * Set the cell's using this style to be locked + */ + BooleanEnum locked() default BooleanEnum.DEFAULT; + + /** + * Turn on or off "Quote Prefix" or "123 Prefix" for the style, which is used to tell Excel that the thing which + * looks like a number or a formula shouldn't be treated as on. Turning this on is somewhat (but not completely, see + * {@link IgnoredErrorType}) like prefixing the cell value with a ' in Excel + */ + BooleanEnum quotePrefix() default BooleanEnum.DEFAULT; + + /** + * Set the type of horizontal alignment for the cell + */ + HorizontalAlignmentEnum horizontalAlignment() default HorizontalAlignmentEnum.DEFAULT; + + /** + * Set whether the text should be wrapped. Setting this flag to true make all content visible within a + * cell by displaying it on multiple lines + * + */ + BooleanEnum wrapped() default BooleanEnum.DEFAULT; + + /** + * Set the type of vertical alignment for the cell + */ + VerticalAlignmentEnum verticalAlignment() default VerticalAlignmentEnum.DEFAULT; + + /** + * Set the degree of rotation for the text in the cell. + * + * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF uses values from 0 to 180 degrees. The + * implementations of this method will map between these two value-ranges accordingly, however the corresponding + * getter is returning values in the range mandated by the current type of Excel file-format that this CellStyle is + * applied to. + */ + short rotation() default -1; + + /** + * Set the number of spaces to indent the text in the cell + */ + short indent() default -1; + + /** + * Set the type of border to use for the left border of the cell + */ + BorderStyleEnum borderLeft() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the right border of the cell + */ + BorderStyleEnum borderRight() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the top border of the cell + */ + BorderStyleEnum borderTop() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the bottom border of the cell + */ + BorderStyleEnum borderBottom() default BorderStyleEnum.DEFAULT; + + /** + * Set the color to use for the left border + * + * @see IndexedColors + */ + short leftBorderColor() default -1; + + /** + * Set the color to use for the right border + * + * @see IndexedColors + * + */ + short rightBorderColor() default -1; + + /** + * Set the color to use for the top border + * + * @see IndexedColors + * + */ + short topBorderColor() default -1; + + /** + * Set the color to use for the bottom border + * + * @see IndexedColors + * + */ + short bottomBorderColor() default -1; + + /** + * Setting to one fills the cell with the foreground color... No idea about other values + * + * @see FillPatternType#SOLID_FOREGROUND + */ + FillPatternTypeEnum fillPatternType() default FillPatternTypeEnum.DEFAULT; + + /** + * Set the background fill color. + * + * @see IndexedColors + * + */ + short fillBackgroundColor() default -1; + + /** + * Set the foreground fill color Note: Ensure Foreground color is set prior to background color. + * + * @see IndexedColors + * + */ + short fillForegroundColor() default -1; + + /** + * Controls if the Cell should be auto-sized to shrink to fit if the text is too long + */ + BooleanEnum shrinkToFit() default BooleanEnum.DEFAULT; + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadFontStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadFontStyle.java new file mode 100644 index 0000000..c415d2a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadFontStyle.java @@ -0,0 +1,91 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.enums.BooleanEnum; + +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hssf.usermodel.HSSFPalette; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Custom header styles. + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface HeadFontStyle { + + /** + * The name for the font (i.e. Arial) + */ + String fontName() default ""; + + /** + * Height in the familiar unit of measure - points + */ + short fontHeightInPoints() default -1; + + /** + * Whether to use italics or not + */ + BooleanEnum italic() default BooleanEnum.DEFAULT; + + /** + * Whether to use a strikeout horizontal line through the text or not + */ + BooleanEnum strikeout() default BooleanEnum.DEFAULT; + + /** + * The color for the font + * + * @see Font#COLOR_NORMAL + * @see Font#COLOR_RED + * @see HSSFPalette#getColor(short) + * @see IndexedColors + */ + short color() default -1; + + /** + * Set normal, super or subscript. + * + * @see Font#SS_NONE + * @see Font#SS_SUPER + * @see Font#SS_SUB + */ + short typeOffset() default -1; + + /** + * set type of text underlining to use + * + * @see Font#U_NONE + * @see Font#U_SINGLE + * @see Font#U_DOUBLE + * @see Font#U_SINGLE_ACCOUNTING + * @see Font#U_DOUBLE_ACCOUNTING + */ + + byte underline() default -1; + + /** + * Set character-set to use. + * + * @see FontCharset + * @see Font#ANSI_CHARSET + * @see Font#DEFAULT_CHARSET + * @see Font#SYMBOL_CHARSET + */ + int charset() default -1; + + /** + * Bold + */ + BooleanEnum bold() default BooleanEnum.DEFAULT; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadRowHeight.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadRowHeight.java new file mode 100644 index 0000000..c0ae6ee --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadRowHeight.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Set the height of each table + * + * @author Jiaju Zhuang + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface HeadRowHeight { + /** + * Set the header height + *

+ * -1 mean the auto set height + * + * @return Header height + */ + short value() default -1; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadStyle.java new file mode 100644 index 0000000..db2e774 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/HeadStyle.java @@ -0,0 +1,156 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ai.chat2db.excel.enums.BooleanEnum; +import ai.chat2db.excel.enums.poi.BorderStyleEnum; +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; +import ai.chat2db.excel.enums.poi.HorizontalAlignmentEnum; +import ai.chat2db.excel.enums.poi.VerticalAlignmentEnum; + +import org.apache.poi.ss.usermodel.BuiltinFormats; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IgnoredErrorType; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Custom header styles + * + * @author Jiaju Zhuang + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface HeadStyle { + /** + * Set the data format (must be a valid format). Built in formats are defined at {@link BuiltinFormats}. + */ + short dataFormat() default -1; + + /** + * Set the cell's using this style to be hidden + */ + BooleanEnum hidden() default BooleanEnum.DEFAULT; + + /** + * Set the cell's using this style to be locked + */ + BooleanEnum locked() default BooleanEnum.DEFAULT; + + /** + * Turn on or off "Quote Prefix" or "123 Prefix" for the style, which is used to tell Excel that the thing which + * looks like a number or a formula shouldn't be treated as on. Turning this on is somewhat (but not completely, see + * {@link IgnoredErrorType}) like prefixing the cell value with a ' in Excel + */ + BooleanEnum quotePrefix() default BooleanEnum.DEFAULT; + + /** + * Set the type of horizontal alignment for the cell + */ + HorizontalAlignmentEnum horizontalAlignment() default HorizontalAlignmentEnum.DEFAULT; + + /** + * Set whether the text should be wrapped. Setting this flag to true make all content visible within a + * cell by displaying it on multiple lines + */ + BooleanEnum wrapped() default BooleanEnum.DEFAULT; + + /** + * Set the type of vertical alignment for the cell + */ + VerticalAlignmentEnum verticalAlignment() default VerticalAlignmentEnum.DEFAULT; + + /** + * Set the degree of rotation for the text in the cell. + * + * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF uses values from 0 to 180 degrees. The + * implementations of this method will map between these two value-ranges accordingly, however the corresponding + * getter is returning values in the range mandated by the current type of Excel file-format that this CellStyle is + * applied to. + */ + short rotation() default -1; + + /** + * Set the number of spaces to indent the text in the cell + */ + short indent() default -1; + + /** + * Set the type of border to use for the left border of the cell + */ + BorderStyleEnum borderLeft() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the right border of the cell + */ + BorderStyleEnum borderRight() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the top border of the cell + */ + BorderStyleEnum borderTop() default BorderStyleEnum.DEFAULT; + + /** + * Set the type of border to use for the bottom border of the cell + */ + BorderStyleEnum borderBottom() default BorderStyleEnum.DEFAULT; + + /** + * Set the color to use for the left border + * + * @see IndexedColors + */ + short leftBorderColor() default -1; + + /** + * Set the color to use for the right border + * + * @see IndexedColors + */ + short rightBorderColor() default -1; + + /** + * Set the color to use for the top border + * + * @see IndexedColors + */ + short topBorderColor() default -1; + + /** + * Set the color to use for the bottom border + * + * @see IndexedColors + */ + short bottomBorderColor() default -1; + + /** + * Setting to one fills the cell with the foreground color... No idea about other values + * + * @see FillPatternType#SOLID_FOREGROUND + */ + FillPatternTypeEnum fillPatternType() default FillPatternTypeEnum.DEFAULT; + + /** + * Set the background fill color. + * + * @see IndexedColors + */ + short fillBackgroundColor() default -1; + + /** + * Set the foreground fill color Note: Ensure Foreground color is set prior to background color. + * + * @see IndexedColors + */ + short fillForegroundColor() default -1; + + /** + * Controls if the Cell should be auto-sized to shrink to fit if the text is too long + */ + BooleanEnum shrinkToFit() default BooleanEnum.DEFAULT; + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/OnceAbsoluteMerge.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/OnceAbsoluteMerge.java new file mode 100644 index 0000000..3d5bdf8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/annotation/write/style/OnceAbsoluteMerge.java @@ -0,0 +1,45 @@ +package ai.chat2db.excel.annotation.write.style; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Merge the cells once + * + * @author Jiaju Zhuang + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface OnceAbsoluteMerge { + /** + * First row + * + * @return + */ + int firstRowIndex() default -1; + + /** + * Last row + * + * @return + */ + int lastRowIndex() default -1; + + /** + * First column + * + * @return + */ + int firstColumnIndex() default -1; + + /** + * Last row + * + * @return + */ + int lastColumnIndex() default -1; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/Ehcache.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/Ehcache.java new file mode 100644 index 0000000..ccac093 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/Ehcache.java @@ -0,0 +1,163 @@ +package ai.chat2db.excel.cache; + +import java.io.File; +import java.util.ArrayList; +import java.util.UUID; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.ListUtils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.ehcache.CacheManager; +import org.ehcache.config.CacheConfiguration; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.CacheManagerBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; +import org.ehcache.config.units.EntryUnit; +import org.ehcache.config.units.MemoryUnit; + +/** + * Default cache + * + * @author Jiaju Zhuang + */ +@Slf4j +public class Ehcache implements ReadCache { + public static final int BATCH_COUNT = 100; + /** + * Key index + */ + private int activeIndex = 0; + public static final int DEBUG_CACHE_MISS_SIZE = 1000; + public static final int DEBUG_WRITE_SIZE = 100 * 10000; + private ArrayList dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + private static final CacheManager FILE_CACHE_MANAGER; + private static final CacheConfiguration FILE_CACHE_CONFIGURATION; + private static final CacheManager ACTIVE_CACHE_MANAGER; + private static final File CACHE_PATH_FILE; + + private final CacheConfiguration activeCacheConfiguration; + /** + * Bulk storage data + */ + private org.ehcache.Cache fileCache; + /** + * Currently active cache + */ + private org.ehcache.Cache activeCache; + private String cacheAlias; + /** + * Count the number of cache misses + */ + private int cacheMiss = 0; + + @Deprecated + public Ehcache(Integer maxCacheActivateSize) { + this(maxCacheActivateSize, null); + } + + public Ehcache(Integer maxCacheActivateSize, Integer maxCacheActivateBatchCount) { + // In order to be compatible with the code + // If the user set up `maxCacheActivateSize`, then continue using it + if (maxCacheActivateSize != null) { + this.activeCacheConfiguration = CacheConfigurationBuilder + .newCacheConfigurationBuilder(Integer.class, ArrayList.class, + ResourcePoolsBuilder.newResourcePoolsBuilder() + .heap(maxCacheActivateSize, MemoryUnit.MB)) + .build(); + } else { + this.activeCacheConfiguration = CacheConfigurationBuilder + .newCacheConfigurationBuilder(Integer.class, ArrayList.class, + ResourcePoolsBuilder.newResourcePoolsBuilder() + .heap(maxCacheActivateBatchCount, EntryUnit.ENTRIES)) + .build(); + } + } + + static { + CACHE_PATH_FILE = FileUtils.createCacheTmpFile(); + FILE_CACHE_MANAGER = + CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(CACHE_PATH_FILE)).build( + true); + ACTIVE_CACHE_MANAGER = CacheManagerBuilder.newCacheManagerBuilder().build(true); + FILE_CACHE_CONFIGURATION = CacheConfigurationBuilder + .newCacheConfigurationBuilder(Integer.class, ArrayList.class, ResourcePoolsBuilder.newResourcePoolsBuilder() + .disk(20, MemoryUnit.GB)).build(); + } + + @Override + public void init(AnalysisContext analysisContext) { + cacheAlias = UUID.randomUUID().toString(); + try { + fileCache = FILE_CACHE_MANAGER.createCache(cacheAlias, FILE_CACHE_CONFIGURATION); + } catch (IllegalStateException e) { + //fix Issue #2693,Temporary files may be deleted if there is no operation for a long time, so they need + // to be recreated. + if (CACHE_PATH_FILE.exists()) { + throw e; + } + synchronized (Ehcache.class) { + if (!CACHE_PATH_FILE.exists()) { + if (log.isDebugEnabled()) { + log.debug("cache file dir is not exist retry create"); + } + FileUtils.createDirectory(CACHE_PATH_FILE); + } + } + fileCache = FILE_CACHE_MANAGER.createCache(cacheAlias, FILE_CACHE_CONFIGURATION); + } + activeCache = ACTIVE_CACHE_MANAGER.createCache(cacheAlias, activeCacheConfiguration); + } + + @Override + public void put(String value) { + dataList.add(value); + if (dataList.size() >= BATCH_COUNT) { + fileCache.put(activeIndex, dataList); + activeIndex++; + dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + if (log.isDebugEnabled()) { + int alreadyPut = activeIndex * BATCH_COUNT + dataList.size(); + if (alreadyPut % DEBUG_WRITE_SIZE == 0) { + log.debug("Already put :{}", alreadyPut); + } + } + } + + @Override + public String get(Integer key) { + if (key == null || key < 0) { + return null; + } + int route = key / BATCH_COUNT; + ArrayList dataList = activeCache.get(route); + if (dataList == null) { + dataList = fileCache.get(route); + activeCache.put(route, dataList); + if (log.isDebugEnabled()) { + if (cacheMiss++ % DEBUG_CACHE_MISS_SIZE == 0) { + log.debug("Cache misses count:{}", cacheMiss); + } + } + } + return dataList.get(key % BATCH_COUNT); + } + + @Override + public void putFinished() { + if (CollectionUtils.isEmpty(dataList)) { + return; + } + fileCache.put(activeIndex, dataList); + } + + @Override + public void destroy() { + FILE_CACHE_MANAGER.removeCache(cacheAlias); + ACTIVE_CACHE_MANAGER.removeCache(cacheAlias); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/MapCache.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/MapCache.java new file mode 100644 index 0000000..3ab8830 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/MapCache.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.cache; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * Putting temporary data directly into a map is a little more efficient but very memory intensive + * + * @author Jiaju Zhuang + */ +public class MapCache implements ReadCache { + private final List cache = new ArrayList<>(); + + @Override + public void init(AnalysisContext analysisContext) {} + + @Override + public void put(String value) { + cache.add(value); + } + + @Override + public String get(Integer key) { + if (key == null || key < 0) { + return null; + } + return cache.get(key); + } + + @Override + public void putFinished() {} + + @Override + public void destroy() {} + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/ReadCache.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/ReadCache.java new file mode 100644 index 0000000..768765d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/ReadCache.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.cache; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * Read cache + * + * @author Jiaju Zhuang + */ +public interface ReadCache { + + /** + * Initialize cache + * + * @param analysisContext + * A context is the main anchorage point of a excel reader. + */ + void init(AnalysisContext analysisContext); + + /** + * Automatically generate the key and put it in the cache.Key start from 0 + * + * @param value + * Cache value + */ + void put(String value); + + /** + * Get value + * + * @param key + * Index + * @return Value + */ + String get(Integer key); + + /** + * It's called when all the values are put in + */ + void putFinished(); + + /** + * Called when the excel read is complete + */ + void destroy(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/XlsCache.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/XlsCache.java new file mode 100644 index 0000000..fcebae2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/XlsCache.java @@ -0,0 +1,37 @@ +package ai.chat2db.excel.cache; + +import org.apache.poi.hssf.record.SSTRecord; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * + * Use SSTRecord. + * + * @author Jiaju Zhuang + */ +public class XlsCache implements ReadCache { + private final SSTRecord sstRecord; + + public XlsCache(SSTRecord sstRecord) { + this.sstRecord = sstRecord; + } + + @Override + public void init(AnalysisContext analysisContext) {} + + @Override + public void put(String value) {} + + @Override + public String get(Integer key) { + return sstRecord.getString(key).toString(); + } + + @Override + public void putFinished() {} + + @Override + public void destroy() {} + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/EternalReadCacheSelector.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/EternalReadCacheSelector.java new file mode 100644 index 0000000..0879296 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/EternalReadCacheSelector.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.cache.selector; + +import ai.chat2db.excel.cache.ReadCache; +import org.apache.poi.openxml4j.opc.PackagePart; + +/** + * Choose a eternal cache + * + * @author Jiaju Zhuang + **/ +public class EternalReadCacheSelector implements ReadCacheSelector { + private ReadCache readCache; + + public EternalReadCacheSelector(ReadCache readCache) { + this.readCache = readCache; + } + + @Override + public ReadCache readCache(PackagePart sharedStringsTablePackagePart) { + return readCache; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/ReadCacheSelector.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/ReadCacheSelector.java new file mode 100644 index 0000000..6d799a1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/ReadCacheSelector.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.cache.selector; + +import ai.chat2db.excel.cache.ReadCache; +import org.apache.poi.openxml4j.opc.PackagePart; + +/** + * Select the cache + * + * @author Jiaju Zhuang + **/ +public interface ReadCacheSelector { + + /** + * Select a cache + * + * @param sharedStringsTablePackagePart + * @return + */ + ReadCache readCache(PackagePart sharedStringsTablePackagePart); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/SimpleReadCacheSelector.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/SimpleReadCacheSelector.java new file mode 100644 index 0000000..ab1a8bb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/cache/selector/SimpleReadCacheSelector.java @@ -0,0 +1,110 @@ +package ai.chat2db.excel.cache.selector; + +import java.io.IOException; + +import ai.chat2db.excel.cache.Ehcache; +import ai.chat2db.excel.cache.MapCache; +import ai.chat2db.excel.cache.ReadCache; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple cache selector + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class SimpleReadCacheSelector implements ReadCacheSelector { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleReadCacheSelector.class); + /** + * Convert bytes to megabytes + */ + private static final long B2M = 1000 * 1000L; + /** + * If it's less than 5M, use map cache, or use ehcache.unit MB. + */ + private static final long DEFAULT_MAX_USE_MAP_CACHE_SIZE = 5; + + /** + * Maximum batch of `SharedStrings` stored in memory. + * The batch size is 100.{@link Ehcache#BATCH_COUNT} + */ + private static final int DEFAULT_MAX_EHCACHE_ACTIVATE_BATCH_COUNT = 20; + + /** + * Shared strings exceeding this value will use {@link Ehcache},or use {@link MapCache}.unit MB. + */ + private Long maxUseMapCacheSize; + + /** + * Maximum size of cache activation.unit MB. + * + * @deprecated Please use maxCacheActivateBatchCount to control the size of the occupied memory + */ + @Deprecated + private Integer maxCacheActivateSize; + + /** + * Maximum batch of `SharedStrings` stored in memory. + * The batch size is 100.{@link Ehcache#BATCH_COUNT} + */ + private Integer maxCacheActivateBatchCount; + + public SimpleReadCacheSelector() { + } + + /** + * Parameter maxCacheActivateSize has already been abandoned + * + * @param maxUseMapCacheSize + * @param maxCacheActivateSize + */ + @Deprecated + public SimpleReadCacheSelector(Long maxUseMapCacheSize, Integer maxCacheActivateSize) { + this.maxUseMapCacheSize = maxUseMapCacheSize; + this.maxCacheActivateSize = maxCacheActivateSize; + } + + @Override + public ReadCache readCache(PackagePart sharedStringsTablePackagePart) { + long size = sharedStringsTablePackagePart.getSize(); + if (size < 0) { + try { + size = sharedStringsTablePackagePart.getInputStream().available(); + } catch (IOException e) { + LOGGER.warn("Unable to get file size, default used MapCache"); + return new MapCache(); + } + } + if (maxUseMapCacheSize == null) { + maxUseMapCacheSize = DEFAULT_MAX_USE_MAP_CACHE_SIZE; + } + if (size < maxUseMapCacheSize * B2M) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Use map cache.size:{}", size); + } + return new MapCache(); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Use ehcache.size:{}", size); + } + + // In order to be compatible with the code + // If the user set up `maxCacheActivateSize`, then continue using it + if (maxCacheActivateSize != null) { + return new Ehcache(maxCacheActivateSize, maxCacheActivateBatchCount); + } else { + if (maxCacheActivateBatchCount == null) { + maxCacheActivateBatchCount = DEFAULT_MAX_EHCACHE_ACTIVATE_BATCH_COUNT; + } + return new Ehcache(maxCacheActivateSize, maxCacheActivateBatchCount); + } + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/BuiltinFormats.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/BuiltinFormats.java new file mode 100644 index 0000000..3b67c44 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/BuiltinFormats.java @@ -0,0 +1,530 @@ +package ai.chat2db.excel.constant; + +import java.util.Locale; +import java.util.Map; + +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.util.StringUtils; + +/** + * Excel's built-in format conversion.Currently only supports Chinese. + * + *

+ * If it is not Chinese, it is recommended to directly modify the builtinFormats, which will better support + * internationalization in the future. + * + *

+ * Specific correspondence please see: + * https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1 + * + * @author Jiaju Zhuang + **/ +public class BuiltinFormats { + + private static final String RESERVED = "reserved-"; + + public static short GENERAL = 0; + + public static final String[] BUILTIN_FORMATS_ALL_LANGUAGES = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"¥\"#,##0_);(\"¥\"#,##0)", + // 6 + "\"¥\"#,##0_);[Red](\"¥\"#,##0)", + // 7 + "\"¥\"#,##0.00_);(\"¥\"#,##0.00)", + // 8 + "\"¥\"#,##0.00_);[Red](\"¥\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d". + "yyyy/m/d", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy-m-d h:mm". + "yyyy-m-d h:mm", + // 23-36 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + null, + // 28 + null, + // 29 + null, + // 30 + null, + // 31 + null, + // 32 + null, + // 33 + null, + // 34 + null, + // 35 + null, + // 36 + null, + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"¥\"* #,##0_);_(\"¥\"* (#,##0);_(\"¥\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"¥\"* #,##0.00_);_(\"¥\"* (#,##0.00);_(\"¥\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + }; + + public static final String[] BUILTIN_FORMATS_CN = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"¥\"#,##0_);(\"¥\"#,##0)", + // 6 + "\"¥\"#,##0_);[Red](\"¥\"#,##0)", + // 7 + "\"¥\"#,##0.00_);(\"¥\"#,##0.00)", + // 8 + "\"¥\"#,##0.00_);[Red](\"¥\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d". + "yyyy/m/d", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy-m-d h:mm". + "yyyy-m-d h:mm", + // 23-26 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + "yyyy\"年\"m\"月\"", + // 28 + "m\"月\"d\"日\"", + // 29 + "m\"月\"d\"日\"", + // 30 + "m-d-yy", + // 31 + "yyyy\"年\"m\"月\"d\"日\"", + // 32 + "h\"时\"mm\"分\"", + // 33 + "h\"时\"mm\"分\"ss\"秒\"", + // 34 + "上午/下午h\"时\"mm\"分\"", + // 35 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 36 + "yyyy\"年\"m\"月\"", + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"¥\"* #,##0_);_(\"¥\"* (#,##0);_(\"¥\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"¥\"* #,##0.00_);_(\"¥\"* (#,##0.00);_(\"¥\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + // 50 + "yyyy\"年\"m\"月\"", + // 51 + "m\"月\"d\"日\"", + // 52 + "yyyy\"年\"m\"月\"", + // 53 + "m\"月\"d\"日\"", + // 54 + "m\"月\"d\"日\"", + // 55 + "上午/下午h\"时\"mm\"分\"", + // 56 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 57 + "yyyy\"年\"m\"月\"", + // 58 + "m\"月\"d\"日\"", + // 59 + "t0", + // 60 + "t0.00", + // 61 + "t#,##0", + // 62 + "t#,##0.00", + // 63-66 No specific correspondence found in the official documentation. + // 63 + null, + // 64 + null, + // 65 + null, + // 66 + null, + // 67 + "t0%", + // 68 + "t0.00%", + // 69 + "t# ?/?", + // 70 + "t# ??/??", + // 71 + "ว/ด/ปปปป", + // 72 + "ว-ดดด-ปป", + // 73 + "ว-ดดด", + // 74 + "ดดด-ปป", + // 75 + "ช:นน", + // 76 + "ช:นน:ทท", + // 77 + "ว/ด/ปปปป ช:นน", + // 78 + "นน:ทท", + // 79 + "[ช]:นน:ทท", + // 80 + "นน:ทท.0", + // 81 + "d/m/bb", + // end + }; + + public static final String[] BUILTIN_FORMATS_US = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"$\"#,##0_);(\"$\"#,##0)", + // 6 + "\"$\"#,##0_);[Red](\"$\"#,##0)", + // 7 + "\"$\"#,##0.00_);(\"$\"#,##0.00)", + // 8 + "\"$\"#,##0.00_);[Red](\"$\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d". + "yyyy/m/d", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy-m-d h:mm". + "yyyy-m-d h:mm", + // 23-26 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + "yyyy\"年\"m\"月\"", + // 28 + "m\"月\"d\"日\"", + // 29 + "m\"月\"d\"日\"", + // 30 + "m-d-yy", + // 31 + "yyyy\"年\"m\"月\"d\"日\"", + // 32 + "h\"时\"mm\"分\"", + // 33 + "h\"时\"mm\"分\"ss\"秒\"", + // 34 + "上午/下午h\"时\"mm\"分\"", + // 35 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 36 + "yyyy\"年\"m\"月\"", + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + // 50 + "yyyy\"年\"m\"月\"", + // 51 + "m\"月\"d\"日\"", + // 52 + "yyyy\"年\"m\"月\"", + // 53 + "m\"月\"d\"日\"", + // 54 + "m\"月\"d\"日\"", + // 55 + "上午/下午h\"时\"mm\"分\"", + // 56 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 57 + "yyyy\"年\"m\"月\"", + // 58 + "m\"月\"d\"日\"", + // 59 + "t0", + // 60 + "t0.00", + // 61 + "t#,##0", + // 62 + "t#,##0.00", + // 63-66 No specific correspondence found in the official documentation. + // 63 + null, + // 64 + null, + // 65 + null, + // 66 + null, + // 67 + "t0%", + // 68 + "t0.00%", + // 69 + "t# ?/?", + // 70 + "t# ??/??", + // 71 + "ว/ด/ปปปป", + // 72 + "ว-ดดด-ปป", + // 73 + "ว-ดดด", + // 74 + "ดดด-ปป", + // 75 + "ช:นน", + // 76 + "ช:นน:ทท", + // 77 + "ว/ด/ปปปป ช:นน", + // 78 + "นน:ทท", + // 79 + "[ช]:นน:ทท", + // 80 + "นน:ทท.0", + // 81 + "d/m/bb", + // end + }; + + public static final Map BUILTIN_FORMATS_MAP_CN = buildMap(BUILTIN_FORMATS_CN); + public static final Map BUILTIN_FORMATS_MAP_US = buildMap(BUILTIN_FORMATS_US); + public static final short MIN_CUSTOM_DATA_FORMAT_INDEX = 82; + + public static String getBuiltinFormat(Short index, String defaultFormat, Locale locale) { + if (index == null || index <= 0) { + return defaultFormat; + } + + // Give priority to checking if it is the default value for all languages + if (index < BUILTIN_FORMATS_ALL_LANGUAGES.length) { + String format = BUILTIN_FORMATS_ALL_LANGUAGES[index]; + if (format != null) { + return format; + } + } + + // In other cases, give priority to using the externally provided format + if (!StringUtils.isEmpty(defaultFormat) && !defaultFormat.startsWith(RESERVED)) { + return defaultFormat; + } + + // Finally, try using the built-in format + String[] builtinFormat = switchBuiltinFormats(locale); + if (index >= builtinFormat.length) { + return defaultFormat; + } + return builtinFormat[index]; + } + + public static String[] switchBuiltinFormats(Locale locale) { + if (locale != null && Locale.US.getCountry().equals(locale.getCountry())) { + return BUILTIN_FORMATS_US; + } + return BUILTIN_FORMATS_CN; + } + + public static Map switchBuiltinFormatsMap(Locale locale) { + if (locale != null && Locale.US.getCountry().equals(locale.getCountry())) { + return BUILTIN_FORMATS_MAP_US; + } + return BUILTIN_FORMATS_MAP_CN; + } + + private static Map buildMap(String[] builtinFormats) { + Map map = MapUtils.newHashMapWithExpectedSize(builtinFormats.length); + for (int i = 0; i < builtinFormats.length; i++) { + map.put(builtinFormats[i], (short)i); + } + return map; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/EasyExcelConstants.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/EasyExcelConstants.java new file mode 100644 index 0000000..bf03a03 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/EasyExcelConstants.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.constant; + +import java.math.MathContext; +import java.math.RoundingMode; + +/** + * Used to store constant + * + * @author Jiaju Zhuang + */ +public class EasyExcelConstants { + + /** + * Excel by default with 15 to store Numbers, and the double in Java can use to store number 17, led to the accuracy + * will be a problem. So you need to set up 15 to deal with precision + */ + public static final MathContext EXCEL_MATH_CONTEXT = new MathContext(15, RoundingMode.HALF_UP); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/ExcelXmlConstants.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/ExcelXmlConstants.java new file mode 100644 index 0000000..f7479cd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/ExcelXmlConstants.java @@ -0,0 +1,99 @@ +package ai.chat2db.excel.constant; + +/** + * @author jipengfei + */ +public class ExcelXmlConstants { + public static final String DIMENSION_TAG = "dimension"; + public static final String ROW_TAG = "row"; + public static final String CELL_FORMULA_TAG = "f"; + public static final String CELL_VALUE_TAG = "v"; + /** + * When the data is "inlineStr" his tag is "t" + */ + public static final String CELL_INLINE_STRING_VALUE_TAG = "t"; + public static final String CELL_TAG = "c"; + public static final String MERGE_CELL_TAG = "mergeCell"; + public static final String HYPERLINK_TAG = "hyperlink"; + + public static final String X_DIMENSION_TAG = "x:dimension"; + public static final String NS2_DIMENSION_TAG = "ns2:dimension"; + + public static final String X_ROW_TAG = "x:row"; + public static final String NS2_ROW_TAG = "ns2:row"; + + public static final String X_CELL_FORMULA_TAG = "x:f"; + public static final String NS2_CELL_FORMULA_TAG = "ns2:f"; + public static final String X_CELL_VALUE_TAG = "x:v"; + public static final String NS2_CELL_VALUE_TAG = "ns2:v"; + + /** + * When the data is "inlineStr" his tag is "t" + */ + public static final String X_CELL_INLINE_STRING_VALUE_TAG = "x:t"; + public static final String NS2_CELL_INLINE_STRING_VALUE_TAG = "ns2:t"; + + public static final String X_CELL_TAG = "x:c"; + public static final String NS2_CELL_TAG = "ns2:c"; + public static final String X_MERGE_CELL_TAG = "x:mergeCell"; + public static final String NS2_MERGE_CELL_TAG = "ns2:mergeCell"; + public static final String X_HYPERLINK_TAG = "x:hyperlink"; + public static final String NS2_HYPERLINK_TAG = "ns2:hyperlink"; + + /** + * s attribute + */ + public static final String ATTRIBUTE_S = "s"; + /** + * ref attribute + */ + public static final String ATTRIBUTE_REF = "ref"; + /** + * r attribute + */ + public static final String ATTRIBUTE_R = "r"; + /** + * t attribute + */ + public static final String ATTRIBUTE_T = "t"; + /** + * location attribute + */ + public static final String ATTRIBUTE_LOCATION = "location"; + + /** + * rId attribute + */ + public static final String ATTRIBUTE_RID = "r:id"; + + /** + * Cell range split + */ + public static final String CELL_RANGE_SPLIT = ":"; + + // The following is a constant read the `SharedStrings.xml` + + /** + * text + */ + public static final String SHAREDSTRINGS_T_TAG = "t"; + public static final String SHAREDSTRINGS_X_T_TAG = "x:t"; + public static final String SHAREDSTRINGS_NS2_T_TAG = "ns2:t"; + + + /** + * SharedStringItem + */ + public static final String SHAREDSTRINGS_SI_TAG = "si"; + public static final String SHAREDSTRINGS_X_SI_TAG = "x:si"; + public static final String SHAREDSTRINGS_NS2_SI_TAG = "ns2:si"; + + + /** + * Mac 2016 2017 will have this extra field to ignore + */ + public static final String SHAREDSTRINGS_RPH_TAG = "rPh"; + public static final String SHAREDSTRINGS_X_RPH_TAG = "x:rPh"; + public static final String SHAREDSTRINGS_NS2_RPH_TAG = "ns2:rPh"; + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/OrderConstant.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/OrderConstant.java new file mode 100644 index 0000000..b26c15c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/constant/OrderConstant.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.constant; + +/** + * Order constant. + * + * @author Jiaju Zhuang + */ +public class OrderConstant { + + /** + * The system's own style + */ + public static int DEFAULT_DEFINE_STYLE = -70000; + + /** + * Annotation style definition + */ + public static int ANNOTATION_DEFINE_STYLE = -60000; + + /** + * Define style. + */ + public static final int DEFINE_STYLE = -50000; + + /** + * default order. + */ + public static int DEFAULT_ORDER = 0; + + /** + * Sorting of styles written to cells. + */ + public static int FILL_STYLE = 50000; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContext.java new file mode 100644 index 0000000..4a1a4dc --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContext.java @@ -0,0 +1,147 @@ +package ai.chat2db.excel.context; + +import java.io.InputStream; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadHolder; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import ai.chat2db.excel.read.processor.AnalysisEventProcessor; +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * + * A context is the main anchorage point of a excel reader. + * + * @author jipengfei + */ +public interface AnalysisContext { + /** + * Select the current table + * + * @param readSheet + * sheet to read + */ + void currentSheet(ReadSheet readSheet); + + /** + * All information about the workbook you are currently working on + * + * @return Current workbook holder + */ + ReadWorkbookHolder readWorkbookHolder(); + + /** + * All information about the sheet you are currently working on + * + * @return Current sheet holder + */ + ReadSheetHolder readSheetHolder(); + + /** + * Set row of currently operated cell + * + * @param readRowHolder + * Current row holder + */ + void readRowHolder(ReadRowHolder readRowHolder); + + /** + * Row of currently operated cell + * + * @return Current row holder + */ + ReadRowHolder readRowHolder(); + + /** + * The current read operation corresponds to the readSheetHolder or readWorkbookHolder + * + * @return Current holder + */ + ReadHolder currentReadHolder(); + + /** + * Custom attribute + * + * @return + */ + Object getCustom(); + + /** + * Event processor + * + * @return + */ + AnalysisEventProcessor analysisEventProcessor(); + + /** + * Data that the customer needs to read + * + * @return + */ + List readSheetList(); + + /** + * Data that the customer needs to read + * + * @param readSheetList + */ + void readSheetList(List readSheetList); + + /** + * + * get excel type + * + * @return excel type + * @deprecated please use {@link #readWorkbookHolder()} + */ + @Deprecated + ExcelTypeEnum getExcelType(); + + /** + * get in io + * + * @return file io + * @deprecated please use {@link #readWorkbookHolder()} + */ + @Deprecated + InputStream getInputStream(); + + /** + * get current row + * + * @return + * @deprecated please use {@link #readRowHolder()} + */ + @Deprecated + Integer getCurrentRowNum(); + + /** + * get total row ,Data may be inaccurate + * + * @return + * @deprecated please use {@link #readRowHolder()} + */ + @Deprecated + Integer getTotalCount(); + + /** + * get current result + * + * @return get current result + * @deprecated please use {@link #readRowHolder()} + */ + @Deprecated + Object getCurrentRowAnalysisResult(); + + /** + * Interrupt execution + * + * @deprecated please use {@link AnalysisEventListener#hasNext(AnalysisContext)} + */ + @Deprecated + void interrupt(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContextImpl.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContextImpl.java new file mode 100644 index 0000000..311ba1a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/AnalysisContextImpl.java @@ -0,0 +1,174 @@ +package ai.chat2db.excel.context; + +import java.io.InputStream; +import java.util.List; + +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.ReadHolder; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; +import ai.chat2db.excel.read.processor.AnalysisEventProcessor; +import ai.chat2db.excel.read.processor.DefaultAnalysisEventProcessor; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author jipengfei + */ +@Slf4j +public class AnalysisContextImpl implements AnalysisContext { + /** + * The Workbook currently written + */ + private ReadWorkbookHolder readWorkbookHolder; + /** + * Current sheet holder + */ + private ReadSheetHolder readSheetHolder; + /** + * Current row holder + */ + private ReadRowHolder readRowHolder; + /** + * Configuration of currently operated cell + */ + private ReadHolder currentReadHolder; + /** + * Event processor + */ + private final AnalysisEventProcessor analysisEventProcessor; + + public AnalysisContextImpl(ReadWorkbook readWorkbook, ExcelTypeEnum actualExcelType) { + if (readWorkbook == null) { + throw new IllegalArgumentException("Workbook argument cannot be null"); + } + switch (actualExcelType) { + case XLS: + readWorkbookHolder = new XlsReadWorkbookHolder(readWorkbook); + break; + case XLSX: + readWorkbookHolder = new XlsxReadWorkbookHolder(readWorkbook); + break; + case CSV: + readWorkbookHolder = new CsvReadWorkbookHolder(readWorkbook); + break; + default: + break; + } + currentReadHolder = readWorkbookHolder; + analysisEventProcessor = new DefaultAnalysisEventProcessor(); + if (log.isDebugEnabled()) { + log.debug("Initialization 'AnalysisContextImpl' complete"); + } + } + + @Override + public void currentSheet(ReadSheet readSheet) { + switch (readWorkbookHolder.getExcelType()) { + case XLS: + readSheetHolder = new XlsReadSheetHolder(readSheet, readWorkbookHolder); + break; + case XLSX: + readSheetHolder = new XlsxReadSheetHolder(readSheet, readWorkbookHolder); + break; + case CSV: + readSheetHolder = new CsvReadSheetHolder(readSheet, readWorkbookHolder); + break; + default: + break; + } + currentReadHolder = readSheetHolder; + if (readWorkbookHolder.getHasReadSheet().contains(readSheetHolder.getSheetNo())) { + throw new ExcelAnalysisException("Cannot read sheet repeatedly."); + } + readWorkbookHolder.getHasReadSheet().add(readSheetHolder.getSheetNo()); + if (log.isDebugEnabled()) { + log.debug("Began to read:{}", readSheetHolder); + } + } + + @Override + public ReadWorkbookHolder readWorkbookHolder() { + return readWorkbookHolder; + } + + @Override + public ReadSheetHolder readSheetHolder() { + return readSheetHolder; + } + + @Override + public ReadRowHolder readRowHolder() { + return readRowHolder; + } + + @Override + public void readRowHolder(ReadRowHolder readRowHolder) { + this.readRowHolder = readRowHolder; + } + + @Override + public ReadHolder currentReadHolder() { + return currentReadHolder; + } + + @Override + public Object getCustom() { + return readWorkbookHolder.getCustomObject(); + } + + @Override + public AnalysisEventProcessor analysisEventProcessor() { + return analysisEventProcessor; + } + + @Override + public List readSheetList() { + return null; + } + + @Override + public void readSheetList(List readSheetList) { + + } + + @Override + public ExcelTypeEnum getExcelType() { + return readWorkbookHolder.getExcelType(); + } + + @Override + public InputStream getInputStream() { + return readWorkbookHolder.getInputStream(); + } + + @Override + public Integer getCurrentRowNum() { + return readRowHolder.getRowIndex(); + } + + @Override + public Integer getTotalCount() { + return readSheetHolder.getTotal(); + } + + @Override + public Object getCurrentRowAnalysisResult() { + return readRowHolder.getCurrentRowAnalysisResult(); + } + + @Override + public void interrupt() { + throw new ExcelAnalysisException("interrupt error"); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContext.java new file mode 100644 index 0000000..daf2ec0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContext.java @@ -0,0 +1,110 @@ +package ai.chat2db.excel.context; + +import java.io.OutputStream; + +import ai.chat2db.excel.enums.WriteTypeEnum; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.holder.WriteHolder; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * Write context + * + * @author jipengfei + */ +public interface WriteContext { + /** + * If the current sheet already exists, select it; if not, create it + * + * @param writeSheet + * Current sheet + * @param writeType + */ + void currentSheet(WriteSheet writeSheet, WriteTypeEnum writeType); + + /** + * If the current table already exists, select it; if not, create it + * + * @param writeTable + */ + void currentTable(WriteTable writeTable); + + /** + * All information about the workbook you are currently working on + * + * @return + */ + WriteWorkbookHolder writeWorkbookHolder(); + + /** + * All information about the sheet you are currently working on + * + * @return + */ + WriteSheetHolder writeSheetHolder(); + + /** + * All information about the table you are currently working on + * + * @return + */ + WriteTableHolder writeTableHolder(); + + /** + * Configuration of currently operated cell. May be 'writeSheetHolder' or 'writeTableHolder' or + * 'writeWorkbookHolder' + * + * @return + */ + WriteHolder currentWriteHolder(); + + /** + * close + * + * @param onException + */ + void finish(boolean onException); + + /** + * Current sheet + * + * @return + * @deprecated please us e{@link #writeSheetHolder()} + */ + @Deprecated + Sheet getCurrentSheet(); + + /** + * Need head + * + * @return + * @deprecated please us e{@link #writeSheetHolder()} + */ + @Deprecated + boolean needHead(); + + /** + * Get outputStream + * + * @return + * @deprecated please us e{@link #writeWorkbookHolder()} ()} + */ + @Deprecated + OutputStream getOutputStream(); + + /** + * Get workbook + * + * @return + * @deprecated please us e{@link #writeWorkbookHolder()} ()} + */ + @Deprecated + Workbook getWorkbook(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContextImpl.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContextImpl.java new file mode 100644 index 0000000..9aff0e6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/WriteContextImpl.java @@ -0,0 +1,525 @@ +package ai.chat2db.excel.context; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.UUID; + +import ai.chat2db.excel.enums.WriteTypeEnum; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.metadata.CellRange; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.SheetWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.NumberDataFormatterUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.util.WriteHandlerUtils; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.WriteWorkbook; +import ai.chat2db.excel.write.metadata.holder.WriteHolder; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; +import ai.chat2db.excel.write.property.ExcelWriteHeadProperty; + +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; +import org.apache.poi.poifs.crypt.Encryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A context is the main anchorage point of a excel writer. + * + * @author jipengfei + */ +public class WriteContextImpl implements WriteContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteContextImpl.class); + private static final String NO_SHEETS = "no sheets"; + + /** + * The Workbook currently written + */ + private WriteWorkbookHolder writeWorkbookHolder; + /** + * Current sheet holder + */ + private WriteSheetHolder writeSheetHolder; + /** + * The table currently written + */ + private WriteTableHolder writeTableHolder; + /** + * Configuration of currently operated cell + */ + private WriteHolder currentWriteHolder; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; + + public WriteContextImpl(WriteWorkbook writeWorkbook) { + if (writeWorkbook == null) { + throw new IllegalArgumentException("Workbook argument cannot be null"); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Begin to Initialization 'WriteContextImpl'"); + } + initCurrentWorkbookHolder(writeWorkbook); + + WorkbookWriteHandlerContext workbookWriteHandlerContext = WriteHandlerUtils.createWorkbookWriteHandlerContext( + this); + WriteHandlerUtils.beforeWorkbookCreate(workbookWriteHandlerContext); + try { + WorkBookUtil.createWorkBook(writeWorkbookHolder); + } catch (Exception e) { + throw new ExcelGenerateException("Create workbook failure", e); + } + WriteHandlerUtils.afterWorkbookCreate(workbookWriteHandlerContext); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Initialization 'WriteContextImpl' complete"); + } + } + + private void initCurrentWorkbookHolder(WriteWorkbook writeWorkbook) { + writeWorkbookHolder = new WriteWorkbookHolder(writeWorkbook); + currentWriteHolder = writeWorkbookHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeWorkbookHolder"); + } + } + + /** + * @param writeSheet + */ + @Override + public void currentSheet(WriteSheet writeSheet, WriteTypeEnum writeType) { + if (writeSheet == null) { + throw new IllegalArgumentException("Sheet argument cannot be null"); + } + if (selectSheetFromCache(writeSheet)) { + return; + } + + initCurrentSheetHolder(writeSheet); + + // Workbook handler need to supplementary execution + WorkbookWriteHandlerContext workbookWriteHandlerContext = WriteHandlerUtils.createWorkbookWriteHandlerContext( + this); + WriteHandlerUtils.beforeWorkbookCreate(workbookWriteHandlerContext, true); + WriteHandlerUtils.afterWorkbookCreate(workbookWriteHandlerContext, true); + + // Initialization current sheet + initSheet(writeType); + } + + private boolean selectSheetFromCache(WriteSheet writeSheet) { + writeSheetHolder = null; + Integer sheetNo = writeSheet.getSheetNo(); + if (sheetNo == null && StringUtils.isEmpty(writeSheet.getSheetName())) { + sheetNo = 0; + } + if (sheetNo != null) { + writeSheetHolder = writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().get(sheetNo); + } + if (writeSheetHolder == null && !StringUtils.isEmpty(writeSheet.getSheetName())) { + writeSheetHolder = writeWorkbookHolder.getHasBeenInitializedSheetNameMap().get(writeSheet.getSheetName()); + } + if (writeSheetHolder == null) { + return false; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Sheet:{},{} is already existed", writeSheet.getSheetNo(), writeSheet.getSheetName()); + } + writeSheetHolder.setNewInitialization(Boolean.FALSE); + writeTableHolder = null; + currentWriteHolder = writeSheetHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeSheetHolder"); + } + return true; + } + + private void initCurrentSheetHolder(WriteSheet writeSheet) { + writeSheetHolder = new WriteSheetHolder(writeSheet, writeWorkbookHolder); + writeTableHolder = null; + currentWriteHolder = writeSheetHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeSheetHolder"); + } + } + + private void initSheet(WriteTypeEnum writeType) { + SheetWriteHandlerContext sheetWriteHandlerContext = WriteHandlerUtils.createSheetWriteHandlerContext(this); + WriteHandlerUtils.beforeSheetCreate(sheetWriteHandlerContext); + Sheet currentSheet; + try { + if (writeSheetHolder.getSheetNo() != null) { + // When the add default sort order of appearance + if (WriteTypeEnum.ADD.equals(writeType) && writeWorkbookHolder.getTempTemplateInputStream() == null) { + currentSheet = createSheet(); + } else { + currentSheet = writeWorkbookHolder.getWorkbook().getSheetAt(writeSheetHolder.getSheetNo()); + writeSheetHolder + .setCachedSheet( + writeWorkbookHolder.getCachedWorkbook().getSheetAt(writeSheetHolder.getSheetNo())); + } + } else { + // sheet name must not null + currentSheet = writeWorkbookHolder.getWorkbook().getSheet(writeSheetHolder.getSheetName()); + writeSheetHolder + .setCachedSheet(writeWorkbookHolder.getCachedWorkbook().getSheet(writeSheetHolder.getSheetName())); + } + } catch (IllegalArgumentException e) { + if (e.getMessage() != null && e.getMessage().contains(NO_SHEETS)) { + currentSheet = createSheet(); + } else { + throw e; + } + } + if (currentSheet == null) { + currentSheet = createSheet(); + } + writeSheetHolder.setSheet(currentSheet); + WriteHandlerUtils.afterSheetCreate(sheetWriteHandlerContext); + if (WriteTypeEnum.ADD.equals(writeType)) { + // Initialization head + initHead(writeSheetHolder.excelWriteHeadProperty()); + } + writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().put(writeSheetHolder.getSheetNo(), writeSheetHolder); + writeWorkbookHolder.getHasBeenInitializedSheetNameMap().put(writeSheetHolder.getSheetName(), writeSheetHolder); + } + + private Sheet createSheet() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Can not find sheet:{} ,now create it", writeSheetHolder.getSheetNo()); + } + if (StringUtils.isEmpty(writeSheetHolder.getSheetName())) { + writeSheetHolder.setSheetName(writeSheetHolder.getSheetNo().toString()); + } + Sheet currentSheet = + WorkBookUtil.createSheet(writeWorkbookHolder.getWorkbook(), writeSheetHolder.getSheetName()); + writeSheetHolder.setCachedSheet(currentSheet); + return currentSheet; + } + + public void initHead(ExcelWriteHeadProperty excelWriteHeadProperty) { + if (!currentWriteHolder.needHead() || !currentWriteHolder.excelWriteHeadProperty().hasHead()) { + return; + } + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + newRowIndex += currentWriteHolder.relativeHeadRowIndex(); + // Combined head + if (currentWriteHolder.automaticMergeHead()) { + addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + } + for (int relativeRowIndex = 0, i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + + newRowIndex; i++, relativeRowIndex++) { + + RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(this, + newRowIndex, relativeRowIndex, Boolean.TRUE); + WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext); + + Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); + rowWriteHandlerContext.setRow(row); + + WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext); + addOneRowOfHeadDataToExcel(row, i, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); + WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext); + } + } + + private void addMergedRegionToCurrentSheet(ExcelWriteHeadProperty excelWriteHeadProperty, int rowIndex) { + for (CellRange cellRangeModel : excelWriteHeadProperty.headCellRangeList()) { + writeSheetHolder.getSheet() + .addMergedRegionUnsafe(new CellRangeAddress(cellRangeModel.getFirstRow() + rowIndex, + cellRangeModel.getLastRow() + rowIndex, cellRangeModel.getFirstCol(), cellRangeModel.getLastCol())); + } + } + + private void addOneRowOfHeadDataToExcel(Row row, Integer rowIndex, Map headMap, + int relativeRowIndex) { + for (Map.Entry entry : headMap.entrySet()) { + Head head = entry.getValue(); + int columnIndex = entry.getKey(); + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(null, + currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), head.getFieldName(), currentWriteHolder); + + CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(this, row, + rowIndex, head, columnIndex, relativeRowIndex, Boolean.TRUE, excelContentProperty); + WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext); + + Cell cell = row.createCell(columnIndex); + cellWriteHandlerContext.setCell(cell); + + WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext); + + WriteCellData writeCellData = new WriteCellData<>(head.getHeadNameList().get(relativeRowIndex)); + cell.setCellValue(writeCellData.getStringValue()); + cellWriteHandlerContext.setCellDataList(ListUtils.newArrayList(writeCellData)); + cellWriteHandlerContext.setFirstCellData(writeCellData); + + WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext); + } + } + + @Override + public void currentTable(WriteTable writeTable) { + if (writeTable == null) { + return; + } + if (writeTable.getTableNo() == null || writeTable.getTableNo() <= 0) { + writeTable.setTableNo(0); + } + if (writeSheetHolder.getHasBeenInitializedTable().containsKey(writeTable.getTableNo())) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Table:{} is already existed", writeTable.getTableNo()); + } + writeTableHolder = writeSheetHolder.getHasBeenInitializedTable().get(writeTable.getTableNo()); + writeTableHolder.setNewInitialization(Boolean.FALSE); + currentWriteHolder = writeTableHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeTableHolder"); + } + return; + } + + initCurrentTableHolder(writeTable); + + // Workbook and sheet handler need to supplementary execution + WorkbookWriteHandlerContext workbookWriteHandlerContext = WriteHandlerUtils.createWorkbookWriteHandlerContext( + this); + WriteHandlerUtils.beforeWorkbookCreate(workbookWriteHandlerContext, true); + WriteHandlerUtils.afterWorkbookCreate(workbookWriteHandlerContext, true); + + SheetWriteHandlerContext sheetWriteHandlerContext = WriteHandlerUtils.createSheetWriteHandlerContext(this); + WriteHandlerUtils.beforeSheetCreate(sheetWriteHandlerContext, true); + WriteHandlerUtils.afterSheetCreate(sheetWriteHandlerContext, true); + + initHead(writeTableHolder.excelWriteHeadProperty()); + } + + private void initCurrentTableHolder(WriteTable writeTable) { + writeTableHolder = new WriteTableHolder(writeTable, writeSheetHolder); + writeSheetHolder.getHasBeenInitializedTable().put(writeTable.getTableNo(), writeTableHolder); + currentWriteHolder = writeTableHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeTableHolder"); + } + } + + @Override + public WriteWorkbookHolder writeWorkbookHolder() { + return writeWorkbookHolder; + } + + @Override + public WriteSheetHolder writeSheetHolder() { + return writeSheetHolder; + } + + @Override + public WriteTableHolder writeTableHolder() { + return writeTableHolder; + } + + @Override + public WriteHolder currentWriteHolder() { + return currentWriteHolder; + } + + @Override + public void finish(boolean onException) { + if (finished) { + return; + } + finished = true; + WriteHandlerUtils.afterWorkbookDispose(writeWorkbookHolder.getWorkbookWriteHandlerContext()); + if (writeWorkbookHolder == null) { + return; + } + Throwable throwable = null; + boolean isOutputStreamEncrypt = false; + // Determine if you need to write excel + boolean writeExcel = !onException; + if (writeWorkbookHolder.getWriteExcelOnException()) { + writeExcel = Boolean.TRUE; + } + // No data is written if an exception is thrown + if (writeExcel) { + try { + isOutputStreamEncrypt = doOutputStreamEncrypt07(); + } catch (Throwable t) { + throwable = t; + } + } + if (!isOutputStreamEncrypt) { + try { + if (writeExcel) { + writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + } + writeWorkbookHolder.getWorkbook().close(); + } catch (Throwable t) { + throwable = t; + } + } + try { + Workbook workbook = writeWorkbookHolder.getWorkbook(); + if (workbook instanceof SXSSFWorkbook) { + ((SXSSFWorkbook)workbook).dispose(); + } + } catch (Throwable t) { + throwable = t; + } + try { + if (writeWorkbookHolder.getAutoCloseStream() && writeWorkbookHolder.getOutputStream() != null) { + writeWorkbookHolder.getOutputStream().close(); + } + } catch (Throwable t) { + throwable = t; + } + if (writeExcel && !isOutputStreamEncrypt) { + try { + doFileEncrypt07(); + } catch (Throwable t) { + throwable = t; + } + } + try { + if (writeWorkbookHolder.getTempTemplateInputStream() != null) { + writeWorkbookHolder.getTempTemplateInputStream().close(); + } + } catch (Throwable t) { + throwable = t; + } + clearEncrypt03(); + removeThreadLocalCache(); + if (throwable != null) { + throw new ExcelGenerateException("Can not close IO.", throwable); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Finished write."); + } + } + + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); + ClassUtils.removeThreadLocalCache(); + } + + @Override + public Sheet getCurrentSheet() { + return writeSheetHolder.getSheet(); + } + + @Override + public boolean needHead() { + return writeSheetHolder.needHead(); + } + + @Override + public OutputStream getOutputStream() { + return writeWorkbookHolder.getOutputStream(); + } + + @Override + public Workbook getWorkbook() { + return writeWorkbookHolder.getWorkbook(); + } + + private void clearEncrypt03() { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLS.equals(writeWorkbookHolder.getExcelType())) { + return; + } + Biff8EncryptionKey.setCurrentUserPassword(null); + } + + /** + * To encrypt + */ + private boolean doOutputStreamEncrypt07() throws Exception { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + return false; + } + if (writeWorkbookHolder.getFile() != null) { + return false; + } + File tempXlsx = FileUtils.createTmpFile(UUID.randomUUID() + ".xlsx"); + FileOutputStream tempFileOutputStream = new FileOutputStream(tempXlsx); + try { + writeWorkbookHolder.getWorkbook().write(tempFileOutputStream); + } finally { + try { + writeWorkbookHolder.getWorkbook().close(); + tempFileOutputStream.close(); + } catch (Exception e) { + if (!tempXlsx.delete()) { + throw new ExcelGenerateException("Can not delete temp File!"); + } + throw e; + } + } + try (POIFSFileSystem fileSystem = openFileSystemAndEncrypt(tempXlsx)) { + fileSystem.writeFilesystem(writeWorkbookHolder.getOutputStream()); + } finally { + if (!tempXlsx.delete()) { + throw new ExcelGenerateException("Can not delete temp File!"); + } + } + return true; + } + + /** + * To encrypt + */ + private void doFileEncrypt07() throws Exception { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + return; + } + if (writeWorkbookHolder.getFile() == null) { + return; + } + try (POIFSFileSystem fileSystem = openFileSystemAndEncrypt(writeWorkbookHolder.getFile()); + FileOutputStream fileOutputStream = new FileOutputStream(writeWorkbookHolder.getFile())) { + fileSystem.writeFilesystem(fileOutputStream); + } + } + + private POIFSFileSystem openFileSystemAndEncrypt(File file) throws Exception { + POIFSFileSystem fileSystem = new POIFSFileSystem(); + Encryptor encryptor = new EncryptionInfo(EncryptionMode.standard).getEncryptor(); + encryptor.confirmPassword(writeWorkbookHolder.getPassword()); + try (OPCPackage opcPackage = OPCPackage.open(file, PackageAccess.READ_WRITE); + OutputStream outputStream = encryptor.getDataStream(fileSystem)) { + opcPackage.save(outputStream); + } + return fileSystem; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/CsvReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/CsvReadContext.java new file mode 100644 index 0000000..0dbfcb7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/CsvReadContext.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.context.csv; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; + +/** + * A context is the main anchorage point of a ls xls reader. + * + * @author Jiaju Zhuang + **/ +public interface CsvReadContext extends AnalysisContext { + /** + * All information about the workbook you are currently working on. + * + * @return Current workbook holder + */ + CsvReadWorkbookHolder csvReadWorkbookHolder(); + + /** + * All information about the sheet you are currently working on. + * + * @return Current sheet holder + */ + CsvReadSheetHolder csvReadSheetHolder(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/DefaultCsvReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/DefaultCsvReadContext.java new file mode 100644 index 0000000..fe63778 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/csv/DefaultCsvReadContext.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.context.csv; + +import ai.chat2db.excel.context.AnalysisContextImpl; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * A context is the main anchorage point of a ls xls reader. + * + * @author Jiaju Zhuang + */ +public class DefaultCsvReadContext extends AnalysisContextImpl implements CsvReadContext { + + public DefaultCsvReadContext(ReadWorkbook readWorkbook, ExcelTypeEnum actualExcelType) { + super(readWorkbook, actualExcelType); + } + + @Override + public CsvReadWorkbookHolder csvReadWorkbookHolder() { + return (CsvReadWorkbookHolder)readWorkbookHolder(); + } + + @Override + public CsvReadSheetHolder csvReadSheetHolder() { + return (CsvReadSheetHolder)readSheetHolder(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/DefaultXlsReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/DefaultXlsReadContext.java new file mode 100644 index 0000000..d22c6a3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/DefaultXlsReadContext.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.context.xls; + +import ai.chat2db.excel.context.AnalysisContextImpl; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * + * A context is the main anchorage point of a ls xls reader. + * + * @author Jiaju Zhuang + */ +public class DefaultXlsReadContext extends AnalysisContextImpl implements XlsReadContext { + + public DefaultXlsReadContext(ReadWorkbook readWorkbook, ExcelTypeEnum actualExcelType) { + super(readWorkbook, actualExcelType); + } + + @Override + public XlsReadWorkbookHolder xlsReadWorkbookHolder() { + return (XlsReadWorkbookHolder)readWorkbookHolder(); + } + + @Override + public XlsReadSheetHolder xlsReadSheetHolder() { + return (XlsReadSheetHolder)readSheetHolder(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/XlsReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/XlsReadContext.java new file mode 100644 index 0000000..ead8069 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xls/XlsReadContext.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.context.xls; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xls.XlsReadWorkbookHolder; + +/** + * A context is the main anchorage point of a ls xls reader. + * + * @author Jiaju Zhuang + **/ +public interface XlsReadContext extends AnalysisContext { + /** + * All information about the workbook you are currently working on. + * + * @return Current workbook holder + */ + XlsReadWorkbookHolder xlsReadWorkbookHolder(); + + /** + * All information about the sheet you are currently working on. + * + * @return Current sheet holder + */ + XlsReadSheetHolder xlsReadSheetHolder(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/DefaultXlsxReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/DefaultXlsxReadContext.java new file mode 100644 index 0000000..cedf715 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/DefaultXlsxReadContext.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.context.xlsx; + +import ai.chat2db.excel.context.AnalysisContextImpl; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * + * A context is the main anchorage point of a ls xls reader. + * + * @author Jiaju Zhuang + */ +public class DefaultXlsxReadContext extends AnalysisContextImpl implements XlsxReadContext { + + public DefaultXlsxReadContext(ReadWorkbook readWorkbook, ExcelTypeEnum actualExcelType) { + super(readWorkbook, actualExcelType); + } + + @Override + public XlsxReadWorkbookHolder xlsxReadWorkbookHolder() { + return (XlsxReadWorkbookHolder)readWorkbookHolder(); + } + + @Override + public XlsxReadSheetHolder xlsxReadSheetHolder() { + return (XlsxReadSheetHolder)readSheetHolder(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/XlsxReadContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/XlsxReadContext.java new file mode 100644 index 0000000..f1f4280 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/context/xlsx/XlsxReadContext.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.context.xlsx; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; + +/** + * A context is the main anchorage point of a ls xlsx reader. + * + * @author Jiaju Zhuang + **/ +public interface XlsxReadContext extends AnalysisContext { + /** + * All information about the workbook you are currently working on. + * + * @return Current workbook holder + */ + XlsxReadWorkbookHolder xlsxReadWorkbookHolder(); + + /** + * All information about the sheet you are currently working on. + * + * @return Current sheet holder + */ + XlsxReadSheetHolder xlsxReadSheetHolder(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/AutoConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/AutoConverter.java new file mode 100644 index 0000000..bf20b1d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/AutoConverter.java @@ -0,0 +1,9 @@ +package ai.chat2db.excel.converters; + +/** + * An empty converter.It's automatically converted by type. + * + * @author Jiaju Zhuang + */ +public class AutoConverter implements Converter { +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/Converter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/Converter.java new file mode 100644 index 0000000..85bcbba --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/Converter.java @@ -0,0 +1,86 @@ +package ai.chat2db.excel.converters; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Convert between Java objects and excel objects + * + * @param + * @author Dan Zheng + */ +public interface Converter { + + /** + * Back to object types in Java + * + * @return Support for Java class + */ + default Class supportJavaTypeKey() { + throw new UnsupportedOperationException("The current operation is not supported by the current converter."); + } + + /** + * Back to object enum in excel + * + * @return Support for {@link CellDataTypeEnum} + */ + default CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("The current operation is not supported by the current converter."); + } + + /** + * Convert excel objects to Java objects + * + * @param cellData Excel cell data.NotNull. + * @param contentProperty Content property.Nullable. + * @param globalConfiguration Global configuration.NotNull. + * @return Data to put into a Java object + * @throws Exception Exception. + */ + default T convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + throw new UnsupportedOperationException("The current operation is not supported by the current converter."); + } + + /** + * Convert excel objects to Java objects + * + * @param context read converter context + * @return Data to put into a Java object + * @throws Exception Exception. + */ + default T convertToJavaData(ReadConverterContext context) throws Exception { + return convertToJavaData(context.getReadCellData(), context.getContentProperty(), + context.getAnalysisContext().currentReadHolder().globalConfiguration()); + } + + /** + * Convert Java objects to excel objects + * + * @param value Java Data.NotNull. + * @param contentProperty Content property.Nullable. + * @param globalConfiguration Global configuration.NotNull. + * @return Data to put into a Excel + * @throws Exception Exception. + */ + default WriteCellData convertToExcelData(T value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + throw new UnsupportedOperationException("The current operation is not supported by the current converter."); + } + + /** + * Convert Java objects to excel objects + * + * @param context write context + * @return Data to put into a Excel + * @throws Exception Exception. + */ + default WriteCellData convertToExcelData(WriteConverterContext context) throws Exception { + return convertToExcelData(context.getValue(), context.getContentProperty(), + context.getWriteContext().currentWriteHolder().globalConfiguration()); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ConverterKeyBuild.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ConverterKeyBuild.java new file mode 100644 index 0000000..a2eb59d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ConverterKeyBuild.java @@ -0,0 +1,53 @@ +package ai.chat2db.excel.converters; + +import java.util.Map; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.MapUtils; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Converter unique key.Consider that you can just use class as the key. + * + * @author Jiaju Zhuang + */ +public class ConverterKeyBuild { + + private static final Map, Class> BOXING_MAP = MapUtils.newHashMap(); + + static { + BOXING_MAP.put(int.class, Integer.class); + BOXING_MAP.put(byte.class, Byte.class); + BOXING_MAP.put(long.class, Long.class); + BOXING_MAP.put(double.class, Double.class); + BOXING_MAP.put(float.class, Float.class); + BOXING_MAP.put(char.class, Character.class); + BOXING_MAP.put(short.class, Short.class); + BOXING_MAP.put(boolean.class, Boolean.class); + } + + public static ConverterKey buildKey(Class clazz) { + return buildKey(clazz, null); + } + + public static ConverterKey buildKey(Class clazz, CellDataTypeEnum cellDataTypeEnum) { + Class boxingClass = BOXING_MAP.get(clazz); + if (boxingClass != null) { + return new ConverterKey(boxingClass, cellDataTypeEnum); + } + return new ConverterKey(clazz, cellDataTypeEnum); + } + + @Getter + @Setter + @EqualsAndHashCode + @AllArgsConstructor + public static class ConverterKey { + private Class clazz; + private CellDataTypeEnum cellDataTypeEnum; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/DefaultConverterLoader.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/DefaultConverterLoader.java new file mode 100644 index 0000000..8335ac4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/DefaultConverterLoader.java @@ -0,0 +1,197 @@ +package ai.chat2db.excel.converters; + +import java.util.Map; + +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.converters.ConverterKeyBuild.ConverterKey; +import ai.chat2db.excel.converters.bigdecimal.BigDecimalBooleanConverter; +import ai.chat2db.excel.converters.bigdecimal.BigDecimalNumberConverter; +import ai.chat2db.excel.converters.bigdecimal.BigDecimalStringConverter; +import ai.chat2db.excel.converters.biginteger.BigIntegerBooleanConverter; +import ai.chat2db.excel.converters.biginteger.BigIntegerNumberConverter; +import ai.chat2db.excel.converters.biginteger.BigIntegerStringConverter; +import ai.chat2db.excel.converters.booleanconverter.BooleanBooleanConverter; +import ai.chat2db.excel.converters.booleanconverter.BooleanNumberConverter; +import ai.chat2db.excel.converters.booleanconverter.BooleanStringConverter; +import ai.chat2db.excel.converters.bytearray.BoxingByteArrayImageConverter; +import ai.chat2db.excel.converters.bytearray.ByteArrayImageConverter; +import ai.chat2db.excel.converters.byteconverter.ByteBooleanConverter; +import ai.chat2db.excel.converters.byteconverter.ByteNumberConverter; +import ai.chat2db.excel.converters.byteconverter.ByteStringConverter; +import ai.chat2db.excel.converters.date.DateDateConverter; +import ai.chat2db.excel.converters.date.DateNumberConverter; +import ai.chat2db.excel.converters.date.DateStringConverter; +import ai.chat2db.excel.converters.doubleconverter.DoubleBooleanConverter; +import ai.chat2db.excel.converters.doubleconverter.DoubleNumberConverter; +import ai.chat2db.excel.converters.doubleconverter.DoubleStringConverter; +import ai.chat2db.excel.converters.file.FileImageConverter; +import ai.chat2db.excel.converters.floatconverter.FloatBooleanConverter; +import ai.chat2db.excel.converters.floatconverter.FloatNumberConverter; +import ai.chat2db.excel.converters.floatconverter.FloatStringConverter; +import ai.chat2db.excel.converters.inputstream.InputStreamImageConverter; +import ai.chat2db.excel.converters.integer.IntegerBooleanConverter; +import ai.chat2db.excel.converters.integer.IntegerNumberConverter; +import ai.chat2db.excel.converters.integer.IntegerStringConverter; +import ai.chat2db.excel.converters.localdate.LocalDateDateConverter; +import ai.chat2db.excel.converters.localdate.LocalDateNumberConverter; +import ai.chat2db.excel.converters.localdate.LocalDateStringConverter; +import ai.chat2db.excel.converters.localdatetime.LocalDateTimeNumberConverter; +import ai.chat2db.excel.converters.localdatetime.LocalDateTimeDateConverter; +import ai.chat2db.excel.converters.localdatetime.LocalDateTimeStringConverter; +import ai.chat2db.excel.converters.longconverter.LongBooleanConverter; +import ai.chat2db.excel.converters.longconverter.LongNumberConverter; +import ai.chat2db.excel.converters.longconverter.LongStringConverter; +import ai.chat2db.excel.converters.shortconverter.ShortBooleanConverter; +import ai.chat2db.excel.converters.shortconverter.ShortNumberConverter; +import ai.chat2db.excel.converters.shortconverter.ShortStringConverter; +import ai.chat2db.excel.converters.string.StringBooleanConverter; +import ai.chat2db.excel.converters.string.StringErrorConverter; +import ai.chat2db.excel.converters.string.StringNumberConverter; +import ai.chat2db.excel.converters.string.StringStringConverter; +import ai.chat2db.excel.converters.url.UrlImageConverter; + +/** + * Load default handler + * + * @author Jiaju Zhuang + */ +public class DefaultConverterLoader { + private static Map> defaultWriteConverter; + private static Map> allConverter; + + static { + initDefaultWriteConverter(); + initAllConverter(); + } + + private static void initAllConverter() { + allConverter = MapUtils.newHashMapWithExpectedSize(40); + putAllConverter(new BigDecimalBooleanConverter()); + putAllConverter(new BigDecimalNumberConverter()); + putAllConverter(new BigDecimalStringConverter()); + + putAllConverter(new BigIntegerBooleanConverter()); + putAllConverter(new BigIntegerNumberConverter()); + putAllConverter(new BigIntegerStringConverter()); + + putAllConverter(new BooleanBooleanConverter()); + putAllConverter(new BooleanNumberConverter()); + putAllConverter(new BooleanStringConverter()); + + putAllConverter(new ByteBooleanConverter()); + putAllConverter(new ByteNumberConverter()); + putAllConverter(new ByteStringConverter()); + + putAllConverter(new DateNumberConverter()); + putAllConverter(new DateStringConverter()); + + putAllConverter(new LocalDateNumberConverter()); + putAllConverter(new LocalDateStringConverter()); + + putAllConverter(new LocalDateTimeNumberConverter()); + putAllConverter(new LocalDateTimeStringConverter()); + + putAllConverter(new DoubleBooleanConverter()); + putAllConverter(new DoubleNumberConverter()); + putAllConverter(new DoubleStringConverter()); + + putAllConverter(new FloatBooleanConverter()); + putAllConverter(new FloatNumberConverter()); + putAllConverter(new FloatStringConverter()); + + putAllConverter(new IntegerBooleanConverter()); + putAllConverter(new IntegerNumberConverter()); + putAllConverter(new IntegerStringConverter()); + + putAllConverter(new LongBooleanConverter()); + putAllConverter(new LongNumberConverter()); + putAllConverter(new LongStringConverter()); + + putAllConverter(new ShortBooleanConverter()); + putAllConverter(new ShortNumberConverter()); + putAllConverter(new ShortStringConverter()); + + putAllConverter(new StringBooleanConverter()); + putAllConverter(new StringNumberConverter()); + putAllConverter(new StringStringConverter()); + putAllConverter(new StringErrorConverter()); + } + + private static void initDefaultWriteConverter() { + defaultWriteConverter = MapUtils.newHashMapWithExpectedSize(40); + putWriteConverter(new BigDecimalNumberConverter()); + putWriteConverter(new BigIntegerNumberConverter()); + putWriteConverter(new BooleanBooleanConverter()); + putWriteConverter(new ByteNumberConverter()); + putWriteConverter(new DateDateConverter()); + putWriteConverter(new LocalDateTimeDateConverter()); + putWriteConverter(new LocalDateDateConverter()); + putWriteConverter(new DoubleNumberConverter()); + putWriteConverter(new FloatNumberConverter()); + putWriteConverter(new IntegerNumberConverter()); + putWriteConverter(new LongNumberConverter()); + putWriteConverter(new ShortNumberConverter()); + putWriteConverter(new StringStringConverter()); + putWriteConverter(new FileImageConverter()); + putWriteConverter(new InputStreamImageConverter()); + putWriteConverter(new ByteArrayImageConverter()); + putWriteConverter(new BoxingByteArrayImageConverter()); + putWriteConverter(new UrlImageConverter()); + + // In some cases, it must be converted to string + putWriteStringConverter(new BigDecimalStringConverter()); + putWriteStringConverter(new BigIntegerStringConverter()); + putWriteStringConverter(new BooleanStringConverter()); + putWriteStringConverter(new ByteStringConverter()); + putWriteStringConverter(new DateStringConverter()); + putWriteStringConverter(new LocalDateStringConverter()); + putWriteStringConverter(new LocalDateTimeStringConverter()); + putWriteStringConverter(new DoubleStringConverter()); + putWriteStringConverter(new FloatStringConverter()); + putWriteStringConverter(new IntegerStringConverter()); + putWriteStringConverter(new LongStringConverter()); + putWriteStringConverter(new ShortStringConverter()); + putWriteStringConverter(new StringStringConverter()); + } + + /** + * Load default write converter + * + * @return + */ + public static Map> loadDefaultWriteConverter() { + return defaultWriteConverter; + } + + private static void putWriteConverter(Converter converter) { + defaultWriteConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); + } + + private static void putWriteStringConverter(Converter converter) { + defaultWriteConverter.put( + ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), converter); + } + + /** + * Load default read converter + * + * @return + */ + public static Map> loadDefaultReadConverter() { + return loadAllConverter(); + } + + /** + * Load all converter + * + * @return + */ + public static Map> loadAllConverter() { + return allConverter; + } + + private static void putAllConverter(Converter converter) { + allConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), + converter); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/NullableObjectConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/NullableObjectConverter.java new file mode 100644 index 0000000..5989e49 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/NullableObjectConverter.java @@ -0,0 +1,10 @@ +package ai.chat2db.excel.converters; + +/** + * When implementing convertToExcelData method, pay attention to the reference value may be + * null + * + * @author JiaJu Zhuang + **/ +public interface NullableObjectConverter extends Converter { +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ReadConverterContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ReadConverterContext.java new file mode 100644 index 0000000..26f79ac --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/ReadConverterContext.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.converters; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * read converter context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +public class ReadConverterContext { + /** + * Excel cell data.NotNull. + */ + private ReadCellData readCellData; + /** + * Content property.Nullable. + */ + private ExcelContentProperty contentProperty; + /** + * context.NotNull. + */ + private AnalysisContext analysisContext; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/WriteConverterContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/WriteConverterContext.java new file mode 100644 index 0000000..11b860f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/WriteConverterContext.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.converters; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * write converter context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class WriteConverterContext { + + /** + * Java Data.NotNull. + */ + private T value; + + /** + * Content property.Nullable. + */ + private ExcelContentProperty contentProperty; + + /** + * write context + */ + private WriteContext writeContext; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalBooleanConverter.java new file mode 100644 index 0000000..e41f40a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.bigdecimal; + +import java.math.BigDecimal; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigDecimal and boolean converter + * + * @author Jiaju Zhuang + */ +public class BigDecimalBooleanConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigDecimal.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public BigDecimal convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return BigDecimal.ONE; + } + return BigDecimal.ZERO; + } + + @Override + public WriteCellData convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (BigDecimal.ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalNumberConverter.java new file mode 100644 index 0000000..31fb5c8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalNumberConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.bigdecimal; + +import java.math.BigDecimal; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigDecimal and number converter + * + * @author Jiaju Zhuang + */ +public class BigDecimalNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigDecimal.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public BigDecimal convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue(); + } + + @Override + public WriteCellData convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellData(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalStringConverter.java new file mode 100644 index 0000000..1380831 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bigdecimal/BigDecimalStringConverter.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.converters.bigdecimal; + +import java.math.BigDecimal; +import java.text.ParseException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigDecimal and string converter + * + * @author Jiaju Zhuang + */ +public class BigDecimalStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigDecimal.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public BigDecimal convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseBigDecimal(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerBooleanConverter.java new file mode 100644 index 0000000..7b309cc --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.biginteger; + +import java.math.BigInteger; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigInteger and boolean converter + * + * @author Jiaju Zhuang + */ +public class BigIntegerBooleanConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigInteger.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public BigInteger convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return BigInteger.ONE; + } + return BigInteger.ZERO; + } + + @Override + public WriteCellData convertToExcelData(BigInteger value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (BigInteger.ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerNumberConverter.java new file mode 100644 index 0000000..8c6aa04 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerNumberConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.biginteger; + +import java.math.BigInteger; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigInteger and number converter + * + * @author Jiaju Zhuang + */ +public class BigIntegerNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigInteger.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public BigInteger convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().toBigInteger(); + } + + @Override + public WriteCellData convertToExcelData(BigInteger value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellData(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerStringConverter.java new file mode 100644 index 0000000..77c0245 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/biginteger/BigIntegerStringConverter.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.converters.biginteger; + +import java.math.BigInteger; +import java.text.ParseException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * BigDecimal and string converter + * + * @author Jiaju Zhuang + */ +public class BigIntegerStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return BigInteger.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public BigInteger convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseBigDecimal(cellData.getStringValue(), contentProperty).toBigInteger(); + } + + @Override + public WriteCellData convertToExcelData(BigInteger value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanBooleanConverter.java new file mode 100644 index 0000000..5e3f0d8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanBooleanConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.booleanconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Boolean and boolean converter + * + * @author Jiaju Zhuang + */ +public class BooleanBooleanConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Boolean.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Boolean convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getBooleanValue(); + } + + @Override + public WriteCellData convertToExcelData(Boolean value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanNumberConverter.java new file mode 100644 index 0000000..70c5971 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanNumberConverter.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.converters.booleanconverter; + +import java.math.BigDecimal; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Boolean and number converter + * + * @author Jiaju Zhuang + */ +public class BooleanNumberConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Boolean.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Boolean convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (BigDecimal.ONE.compareTo(cellData.getNumberValue()) == 0) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + @Override + public WriteCellData convertToExcelData(Boolean value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (value) { + return new WriteCellData<>(BigDecimal.ONE); + } + return new WriteCellData<>(BigDecimal.ZERO); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanStringConverter.java new file mode 100644 index 0000000..4879f6a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/booleanconverter/BooleanStringConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.booleanconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Boolean and string converter + * + * @author Jiaju Zhuang + */ +public class BooleanStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Boolean.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Boolean convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return Boolean.valueOf(cellData.getStringValue()); + } + + @Override + public WriteCellData convertToExcelData(Boolean value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value.toString()); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/BoxingByteArrayImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/BoxingByteArrayImageConverter.java new file mode 100644 index 0000000..f7b717a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/BoxingByteArrayImageConverter.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.converters.bytearray; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Boxing Byte array and image converter + * + * @author Jiaju Zhuang + */ +public class BoxingByteArrayImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Byte[].class; + } + + @Override + public WriteCellData convertToExcelData(Byte[] value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + byte[] byteValue = new byte[value.length]; + for (int i = 0; i < value.length; i++) { + byteValue[i] = value[i]; + } + return new WriteCellData<>(byteValue); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/ByteArrayImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/ByteArrayImageConverter.java new file mode 100644 index 0000000..455671e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/bytearray/ByteArrayImageConverter.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.converters.bytearray; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Byte array and image converter + * + * @author Jiaju Zhuang + */ +public class ByteArrayImageConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return byte[].class; + } + + @Override + public WriteCellData convertToExcelData(byte[] value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteBooleanConverter.java new file mode 100644 index 0000000..74cb2d8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.byteconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Byte and boolean converter + * + * @author Jiaju Zhuang + */ +public class ByteBooleanConverter implements Converter { + private static final Byte ONE = (byte)1; + private static final Byte ZERO = (byte)0; + + @Override + public Class supportJavaTypeKey() { + return Byte.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Byte convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Byte value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteNumberConverter.java new file mode 100644 index 0000000..9ee9a96 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteNumberConverter.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.converters.byteconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Byte and number converter + * + * @author Jiaju Zhuang + */ +public class ByteNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Byte.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Byte convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().byteValue(); + } + + @Override + public WriteCellData convertToExcelData(Byte value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellData(value, contentProperty); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteStringConverter.java new file mode 100644 index 0000000..12ed12a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/byteconverter/ByteStringConverter.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.converters.byteconverter; + +import java.text.ParseException; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Byte and string converter + * + * @author Jiaju Zhuang + */ +public class ByteStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Byte.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Byte convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseByte(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Byte value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateDateConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateDateConverter.java new file mode 100644 index 0000000..693a5fb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateDateConverter.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.converters.date; + +import java.util.Date; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Date and date converter + * + * @author Jiaju Zhuang + */ +public class DateDateConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Date.class; + } + + @Override + public WriteCellData convertToExcelData(Date value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + WriteCellData cellData = new WriteCellData<>(value); + String format = null; + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + format = contentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + return cellData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateNumberConverter.java new file mode 100644 index 0000000..831fef9 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateNumberConverter.java @@ -0,0 +1,56 @@ +package ai.chat2db.excel.converters.date; + +import java.math.BigDecimal; +import java.util.Date; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import org.apache.poi.ss.usermodel.DateUtil; + +/** + * Date and number converter + * + * @author Jiaju Zhuang + */ +public class DateNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Date.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Date convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); + } else { + return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()); + } + } + + @Override + public WriteCellData convertToExcelData(Date value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>( + BigDecimal.valueOf(DateUtil.getExcelDate(value, globalConfiguration.getUse1904windowing()))); + } else { + return new WriteCellData<>(BigDecimal.valueOf( + DateUtil.getExcelDate(value, contentProperty.getDateTimeFormatProperty().getUse1904windowing()))); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateStringConverter.java new file mode 100644 index 0000000..8b23c7a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/date/DateStringConverter.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.converters.date; + +import java.text.ParseException; +import java.util.Date; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Date and string converter + * + * @author Jiaju Zhuang + */ +public class DateStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Date.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Date convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.parseDate(cellData.getStringValue(), null); + } else { + return DateUtils.parseDate(cellData.getStringValue(), + contentProperty.getDateTimeFormatProperty().getFormat()); + } + } + + @Override + public WriteCellData convertToExcelData(Date value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>(DateUtils.format(value, null)); + } else { + return new WriteCellData<>(DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat())); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleBooleanConverter.java new file mode 100644 index 0000000..c78396d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.doubleconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Double and boolean converter + * + * @author Jiaju Zhuang + */ +public class DoubleBooleanConverter implements Converter { + private static final Double ONE = 1.0; + private static final Double ZERO = 0.0; + + @Override + public Class supportJavaTypeKey() { + return Double.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Double convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Double value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleNumberConverter.java new file mode 100644 index 0000000..7b55e1f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleNumberConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.doubleconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Double and number converter + * + * @author Jiaju Zhuang + */ +public class DoubleNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Double.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Double convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().doubleValue(); + } + + @Override + public WriteCellData convertToExcelData(Double value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellData(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleStringConverter.java new file mode 100644 index 0000000..3ae0529 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/doubleconverter/DoubleStringConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.doubleconverter; + +import java.text.ParseException; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Double and string converter + * + * @author Jiaju Zhuang + */ +public class DoubleStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Double.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Double convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseDouble(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Double value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/file/FileImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/file/FileImageConverter.java new file mode 100644 index 0000000..b9476dd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/file/FileImageConverter.java @@ -0,0 +1,28 @@ +package ai.chat2db.excel.converters.file; + +import java.io.File; +import java.io.IOException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * File and image converter + * + * @author Jiaju Zhuang + */ +public class FileImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return File.class; + } + + @Override + public WriteCellData convertToExcelData(File value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + return new WriteCellData<>(FileUtils.readFileToByteArray(value)); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatBooleanConverter.java new file mode 100644 index 0000000..4833b9b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.floatconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Float and boolean converter + * + * @author Jiaju Zhuang + */ +public class FloatBooleanConverter implements Converter { + private static final Float ONE = (float)1.0; + private static final Float ZERO = (float)0.0; + + @Override + public Class supportJavaTypeKey() { + return Float.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Float convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Float value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatNumberConverter.java new file mode 100644 index 0000000..413d2b2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatNumberConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.floatconverter; + +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Float and number converter + * + * @author Jiaju Zhuang + */ +public class FloatNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Float.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Float convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().floatValue(); + } + + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return NumberUtils.formatToCellData(context.getValue(), context.getContentProperty()); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatStringConverter.java new file mode 100644 index 0000000..9d6125d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/floatconverter/FloatStringConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.floatconverter; + +import java.text.ParseException; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Float and string converter + * + * @author Jiaju Zhuang + */ +public class FloatStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Float.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Float convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseFloat(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Float value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/inputstream/InputStreamImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/inputstream/InputStreamImageConverter.java new file mode 100644 index 0000000..5bfaf7b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/inputstream/InputStreamImageConverter.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.converters.inputstream; + +import java.io.IOException; +import java.io.InputStream; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.IoUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * File and image converter + * + * @author Jiaju Zhuang + */ +public class InputStreamImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return InputStream.class; + } + + @Override + public WriteCellData convertToExcelData(InputStream value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + return new WriteCellData<>(IoUtils.toByteArray(value)); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerBooleanConverter.java new file mode 100644 index 0000000..1dc064b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.integer; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Integer and boolean converter + * + * @author Jiaju Zhuang + */ +public class IntegerBooleanConverter implements Converter { + private static final Integer ONE = 1; + private static final Integer ZERO = 0; + + @Override + public Class supportJavaTypeKey() { + return Integer.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerNumberConverter.java new file mode 100644 index 0000000..0aff401 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerNumberConverter.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.converters.integer; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Integer and number converter + * + * @author Jiaju Zhuang + */ +public class IntegerNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Integer.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().intValue(); + } + + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return NumberUtils.formatToCellData(context.getValue(), context.getContentProperty()); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerStringConverter.java new file mode 100644 index 0000000..2c53aa4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/integer/IntegerStringConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.integer; + +import java.text.ParseException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Integer and string converter + * + * @author Jiaju Zhuang + */ +public class IntegerStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Integer.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseInteger(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateDateConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateDateConverter.java new file mode 100644 index 0000000..fe42346 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateDateConverter.java @@ -0,0 +1,36 @@ +package ai.chat2db.excel.converters.localdate; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * LocalDate and date converter + * + * @author Jiaju Zhuang + */ +public class LocalDateDateConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalDate.class; + } + + @Override + public WriteCellData convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + LocalDateTime localDateTime = value == null ? null : value.atTime(0, 0); + WriteCellData cellData = new WriteCellData<>(localDateTime); + String format = null; + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + format = contentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultLocalDateFormat); + return cellData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateNumberConverter.java new file mode 100644 index 0000000..65945bd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateNumberConverter.java @@ -0,0 +1,56 @@ +package ai.chat2db.excel.converters.localdate; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import org.apache.poi.ss.usermodel.DateUtil; + +/** + * LocalDate and number converter + * + * @author Jiaju Zhuang + */ +public class LocalDateNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return LocalDate.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public LocalDate convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.getLocalDate(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); + } else { + return DateUtils.getLocalDate(cellData.getNumberValue().doubleValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()); + } + } + + @Override + public WriteCellData convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>( + BigDecimal.valueOf(DateUtil.getExcelDate(value, globalConfiguration.getUse1904windowing()))); + } else { + return new WriteCellData<>(BigDecimal.valueOf( + DateUtil.getExcelDate(value, contentProperty.getDateTimeFormatProperty().getUse1904windowing()))); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateStringConverter.java new file mode 100644 index 0000000..b5dab1f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdate/LocalDateStringConverter.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.converters.localdate; + +import java.text.ParseException; +import java.time.LocalDate; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * LocalDate and string converter + * + * @author Jiaju Zhuang + */ +public class LocalDateStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalDate.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalDate convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.parseLocalDate(cellData.getStringValue(), null, globalConfiguration.getLocale()); + } else { + return DateUtils.parseLocalDate(cellData.getStringValue(), + contentProperty.getDateTimeFormatProperty().getFormat(), globalConfiguration.getLocale()); + } + } + + @Override + public WriteCellData convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>(DateUtils.format(value, null, globalConfiguration.getLocale())); + } else { + return new WriteCellData<>( + DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat(), + globalConfiguration.getLocale())); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeDateConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeDateConverter.java new file mode 100644 index 0000000..534cce0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeDateConverter.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.converters.localdatetime; + +import java.time.LocalDateTime; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * LocalDateTime and date converter + * + * @author Jiaju Zhuang + */ +public class LocalDateTimeDateConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalDateTime.class; + } + + @Override + public WriteCellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws Exception { + WriteCellData cellData = new WriteCellData<>(value); + String format = null; + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + format = contentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + return cellData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeNumberConverter.java new file mode 100644 index 0000000..afc9136 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeNumberConverter.java @@ -0,0 +1,56 @@ +package ai.chat2db.excel.converters.localdatetime; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import org.apache.poi.ss.usermodel.DateUtil; + +/** + * LocalDateTime and number converter + * + * @author Jiaju Zhuang + */ +public class LocalDateTimeNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return LocalDateTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public LocalDateTime convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); + } else { + return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()); + } + } + + @Override + public WriteCellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>( + BigDecimal.valueOf(DateUtil.getExcelDate(value, globalConfiguration.getUse1904windowing()))); + } else { + return new WriteCellData<>(BigDecimal.valueOf( + DateUtil.getExcelDate(value, contentProperty.getDateTimeFormatProperty().getUse1904windowing()))); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeStringConverter.java new file mode 100644 index 0000000..f4939e5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/localdatetime/LocalDateTimeStringConverter.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.converters.localdatetime; + +import java.text.ParseException; +import java.time.LocalDateTime; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * LocalDateTime and string converter + * + * @author Jiaju Zhuang + */ +public class LocalDateTimeStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalDateTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalDateTime convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.parseLocalDateTime(cellData.getStringValue(), null, globalConfiguration.getLocale()); + } else { + return DateUtils.parseLocalDateTime(cellData.getStringValue(), + contentProperty.getDateTimeFormatProperty().getFormat(), globalConfiguration.getLocale()); + } + } + + @Override + public WriteCellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>(DateUtils.format(value, null, globalConfiguration.getLocale())); + } else { + return new WriteCellData<>( + DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat(), + globalConfiguration.getLocale())); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongBooleanConverter.java new file mode 100644 index 0000000..d71f58f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.longconverter; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Long and boolean converter + * + * @author Jiaju Zhuang + */ +public class LongBooleanConverter implements Converter { + private static final Long ONE = 1L; + private static final Long ZERO = 0L; + + @Override + public Class supportJavaTypeKey() { + return Long.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Long convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Long value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongNumberConverter.java new file mode 100644 index 0000000..42443b5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongNumberConverter.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.converters.longconverter; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Long and number converter + * + * @author Jiaju Zhuang + */ +public class LongNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Long.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Long convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().longValue(); + } + + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return NumberUtils.formatToCellData(context.getValue(), context.getContentProperty()); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongStringConverter.java new file mode 100644 index 0000000..2907dfd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/longconverter/LongStringConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.longconverter; + +import java.text.ParseException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Long and string converter + * + * @author Jiaju Zhuang + */ +public class LongStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Long.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Long convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseLong(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Long value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortBooleanConverter.java new file mode 100644 index 0000000..f08f7b6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortBooleanConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.shortconverter; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Short and boolean converter + * + * @author Jiaju Zhuang + */ +public class ShortBooleanConverter implements Converter { + private static final Short ONE = 1; + private static final Short ZERO = 0; + + @Override + public Class supportJavaTypeKey() { + return Short.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public Short convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getBooleanValue()) { + return ONE; + } + return ZERO; + } + + @Override + public WriteCellData convertToExcelData(Short value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (ONE.equals(value)) { + return new WriteCellData<>(Boolean.TRUE); + } + return new WriteCellData<>(Boolean.FALSE); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortNumberConverter.java new file mode 100644 index 0000000..285e802 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortNumberConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.shortconverter; + +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Short and number converter + * + * @author Jiaju Zhuang + */ +public class ShortNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Short.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public Short convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getNumberValue().shortValue(); + } + + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return NumberUtils.formatToCellData(context.getValue(), context.getContentProperty()); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortStringConverter.java new file mode 100644 index 0000000..be61f82 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/shortconverter/ShortStringConverter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.converters.shortconverter; + +import java.text.ParseException; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Short and string converter + * + * @author Jiaju Zhuang + */ +public class ShortStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Short.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Short convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + return NumberUtils.parseShort(cellData.getStringValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(Short value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return NumberUtils.formatToCellDataString(value, contentProperty); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringBooleanConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringBooleanConverter.java new file mode 100644 index 0000000..e5b6817 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringBooleanConverter.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.converters.string; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * String and boolean converter + * + * @author Jiaju Zhuang + */ +public class StringBooleanConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.BOOLEAN; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getBooleanValue().toString(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(Boolean.valueOf(value)); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringErrorConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringErrorConverter.java new file mode 100644 index 0000000..45204c7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringErrorConverter.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.converters.string; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * String and error converter + * + * @author Jiaju Zhuang + */ +public class StringErrorConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.ERROR; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(CellDataTypeEnum.ERROR, value); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringImageConverter.java new file mode 100644 index 0000000..869be84 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringImageConverter.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.converters.string; + +import java.io.File; +import java.io.IOException; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * String and image converter + * + * @author Jiaju Zhuang + */ +public class StringImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + return new WriteCellData<>(FileUtils.readFileToByteArray(new File(value))); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringNumberConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringNumberConverter.java new file mode 100644 index 0000000..29651b2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringNumberConverter.java @@ -0,0 +1,63 @@ +package ai.chat2db.excel.converters.string; + +import java.math.BigDecimal; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.NumberDataFormatterUtils; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * String and number converter + * + * @author Jiaju Zhuang + */ +public class StringNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // If there are "DateTimeFormat", read as date + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + return DateUtils.format(cellData.getNumberValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing(), + contentProperty.getDateTimeFormatProperty().getFormat()); + } + // If there are "NumberFormat", read as number + if (contentProperty != null && contentProperty.getNumberFormatProperty() != null) { + return NumberUtils.format(cellData.getNumberValue(), contentProperty); + } + // Excel defines formatting + boolean hasDataFormatData = cellData.getDataFormatData() != null + && cellData.getDataFormatData().getIndex() != null && !StringUtils.isEmpty( + cellData.getDataFormatData().getFormat()); + if (hasDataFormatData) { + return NumberDataFormatterUtils.format(cellData.getNumberValue(), + cellData.getDataFormatData().getIndex(), cellData.getDataFormatData().getFormat(), globalConfiguration); + } + // Default conversion number + return NumberUtils.format(cellData.getNumberValue(), contentProperty); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(new BigDecimal(value)); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringStringConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringStringConverter.java new file mode 100644 index 0000000..a0aef73 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/string/StringStringConverter.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.converters.string; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * String and string converter + * + * @author Jiaju Zhuang + */ +public class StringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/url/UrlImageConverter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/url/UrlImageConverter.java new file mode 100644 index 0000000..72aedca --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/converters/url/UrlImageConverter.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.converters.url; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.util.IoUtils; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Url and image converter + * + * @author Jiaju Zhuang + * @since 2.1.1 + */ +public class UrlImageConverter implements Converter { + public static int urlConnectTimeout = 1000; + public static int urlReadTimeout = 5000; + + @Override + public Class supportJavaTypeKey() { + return URL.class; + } + + @Override + public WriteCellData convertToExcelData(URL value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + InputStream inputStream = null; + try { + URLConnection urlConnection = value.openConnection(); + urlConnection.setConnectTimeout(urlConnectTimeout); + urlConnection.setReadTimeout(urlReadTimeout); + inputStream = urlConnection.getInputStream(); + byte[] bytes = IoUtils.toByteArray(inputStream); + return new WriteCellData<>(bytes); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/BooleanEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/BooleanEnum.java new file mode 100644 index 0000000..bc585c7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/BooleanEnum.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.enums; + +import lombok.Getter; + +/** + * Default values cannot be used for annotations. + * So an additional an enumeration to determine whether the user has added the enumeration. + * + * @author Jiaju Zhuang + */ +@Getter +public enum BooleanEnum { + /** + * NULL + */ + DEFAULT(null), + /** + * TRUE + */ + TRUE(Boolean.TRUE), + /** + * FALSE + */ + FALSE(Boolean.FALSE), + ; + + Boolean booleanValue; + + BooleanEnum(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ByteOrderMarkEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ByteOrderMarkEnum.java new file mode 100644 index 0000000..1fb29bf --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ByteOrderMarkEnum.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.enums; + +import java.nio.charset.Charset; +import java.util.Map; + +import ai.chat2db.excel.util.MapUtils; + +import lombok.Getter; +import org.apache.commons.io.ByteOrderMark; + +/** + * byte order mark + * + * @author Jiaju Zhuang + */ +@Getter +public enum ByteOrderMarkEnum { + + /** + * UTF_8 + */ + UTF_8(ByteOrderMark.UTF_8), + /** + * UTF_16BE + */ + UTF_16BE(ByteOrderMark.UTF_16BE), + /** + * UTF_16LE + */ + UTF_16LE(ByteOrderMark.UTF_16LE), + /** + * UTF_32BE + */ + UTF_32BE(ByteOrderMark.UTF_32BE), + /** + * UTF_32LE + */ + UTF_32LE(ByteOrderMark.UTF_32LE), + + ; + + final ByteOrderMark byteOrderMark; + final String stringPrefix; + + ByteOrderMarkEnum(ByteOrderMark byteOrderMark) { + this.byteOrderMark = byteOrderMark; + Charset charset = Charset.forName(byteOrderMark.getCharsetName()); + this.stringPrefix = new String(byteOrderMark.getBytes(), charset); + } + + /** + * store character aliases corresponding to `ByteOrderMark` prefix + */ + private static final Map CHARSET_BYTE_ORDER_MARK_MAP = MapUtils.newHashMap(); + + static { + for (ByteOrderMarkEnum value : ByteOrderMarkEnum.values()) { + CHARSET_BYTE_ORDER_MARK_MAP.put(value.getByteOrderMark().getCharsetName(), value); + } + } + + public static ByteOrderMarkEnum valueOfByCharsetName(String charsetName) { + return CHARSET_BYTE_ORDER_MARK_MAP.get(charsetName); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CacheLocationEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CacheLocationEnum.java new file mode 100644 index 0000000..edb1cf2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CacheLocationEnum.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.enums; + +/** + * cache locaciton + * + * @author Jiaju Zhuang + **/ +public enum CacheLocationEnum { + /** + * The cache will be stored in {@code ThreadLocal}, and will be cleared when the excel read and write is completed. + */ + THREAD_LOCAL, + + /** + * The cache will not be cleared unless the app is stopped. + */ + MEMORY, + + /** + * No caching.It may lose some of performance. + */ + NONE; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellDataTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellDataTypeEnum.java new file mode 100644 index 0000000..0e8d069 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellDataTypeEnum.java @@ -0,0 +1,72 @@ +package ai.chat2db.excel.enums; + +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.util.StringUtils; + +/** + * excel internal data type + * + * @author Jiaju Zhuang + */ +public enum CellDataTypeEnum { + /** + * string + */ + STRING, + /** + * This type of data does not need to be read in the 'sharedStrings.xml', it is only used for overuse, and the data + * will be stored as a {@link #STRING} + */ + DIRECT_STRING, + /** + * number + */ + NUMBER, + /** + * boolean + */ + BOOLEAN, + /** + * empty + */ + EMPTY, + /** + * error + */ + ERROR, + /** + * date. Support only when writing. + */ + DATE, + /** + * rich text string.Support only when writing. + */ + RICH_TEXT_STRING, + ; + + private static final Map TYPE_ROUTING_MAP = new HashMap(16); + + static { + TYPE_ROUTING_MAP.put("s", STRING); + TYPE_ROUTING_MAP.put("str", DIRECT_STRING); + TYPE_ROUTING_MAP.put("inlineStr", DIRECT_STRING); + TYPE_ROUTING_MAP.put("e", ERROR); + TYPE_ROUTING_MAP.put("b", BOOLEAN); + TYPE_ROUTING_MAP.put("n", NUMBER); + } + + /** + * Build data types + * + * @param cellType + * @return + */ + public static CellDataTypeEnum buildFromCellType(String cellType) { + if (StringUtils.isEmpty(cellType)) { + return EMPTY; + } + return TYPE_ROUTING_MAP.get(cellType); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellExtraTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellExtraTypeEnum.java new file mode 100644 index 0000000..ac61cd4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/CellExtraTypeEnum.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.enums; + +/** + * Extra data type + * + * @author Jiaju Zhuang + **/ +public enum CellExtraTypeEnum { + /** + * Comment + */ + COMMENT, + /** + * Hyperlink + */ + HYPERLINK, + /** + * Merge + */ + MERGE,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HeadKindEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HeadKindEnum.java new file mode 100644 index 0000000..b8a30c4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HeadKindEnum.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.enums; + +/** + * The types of header + * + * @author Jiaju Zhuang + **/ +public enum HeadKindEnum { + /** + * none + */ + NONE, + /** + * class + */ + CLASS, + /** + * String + */ + STRING; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HolderEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HolderEnum.java new file mode 100644 index 0000000..b705753 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/HolderEnum.java @@ -0,0 +1,25 @@ +package ai.chat2db.excel.enums; + +/** + * The types of holder + * + * @author Jiaju Zhuang + **/ +public enum HolderEnum { + /** + * workbook + */ + WORKBOOK, + /** + * sheet + */ + SHEET, + /** + * table + */ + TABLE, + /** + * row + */ + ROW; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/NumericCellTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/NumericCellTypeEnum.java new file mode 100644 index 0000000..43a1903 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/NumericCellTypeEnum.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.enums; + +import org.apache.poi.ss.usermodel.CellType; + +/** + * Used to supplement {@link CellType}. + * + * Cannot distinguish between date and number in write case. + * + * @author Jiaju Zhuang + */ +public enum NumericCellTypeEnum { + /** + * number + */ + NUMBER, + /** + * date. Support only when writing. + */ + DATE, + ; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ReadDefaultReturnEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ReadDefaultReturnEnum.java new file mode 100644 index 0000000..2d48a0f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/ReadDefaultReturnEnum.java @@ -0,0 +1,37 @@ +package ai.chat2db.excel.enums; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import ai.chat2db.excel.metadata.data.ReadCellData; + +/** + * Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type. + * + * @author Jiaju Zhuang + */ +public enum ReadDefaultReturnEnum { + /** + * default.The content of cells into string, is the same as you see in the excel. + */ + STRING, + + /** + * Returns the actual type. + * Will be automatically selected according to the cell contents what return type, will return the following class: + *
    + *
  1. {@link BigDecimal}
  2. + *
  3. {@link Boolean}
  4. + *
  5. {@link String}
  6. + *
  7. {@link LocalDateTime}
  8. + *
+ */ + ACTUAL_DATA, + + /** + * Return to {@link ReadCellData}, can decide which field you need. + */ + READ_CELL_DATA, + ; + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/RowTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/RowTypeEnum.java new file mode 100644 index 0000000..0ec7cef --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/RowTypeEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.enums; + +/** + * The types of row + * + * @author Jiaju Zhuang + **/ +public enum RowTypeEnum { + /** + * data + */ + DATA, + /** + * empty + */ + EMPTY,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteDirectionEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteDirectionEnum.java new file mode 100644 index 0000000..e29fdc6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteDirectionEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.enums; + +/** + * Direction of writing + * + * @author Jiaju Zhuang + **/ +public enum WriteDirectionEnum { + /** + * Vertical write. + */ + VERTICAL, + /** + * Horizontal write. + */ + HORIZONTAL,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteLastRowTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteLastRowTypeEnum.java new file mode 100644 index 0000000..a9f04e1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteLastRowTypeEnum.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.enums; + +/** + * The types of write last row + * + * @author Jiaju Zhuang + **/ +public enum WriteLastRowTypeEnum { + /** + * Excel are created without templates ,And any data has been written; + */ + COMMON_EMPTY, + /** + * Excel are created with templates ,And any data has been written; + */ + TEMPLATE_EMPTY, + /** + * Any data has been written; + */ + HAS_DATA,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTemplateAnalysisCellTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTemplateAnalysisCellTypeEnum.java new file mode 100644 index 0000000..ddd0b39 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTemplateAnalysisCellTypeEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.enums; + +/** + * Type of template to read when writing + * + * @author Jiaju Zhuang + **/ +public enum WriteTemplateAnalysisCellTypeEnum { + /** + * Common field. + */ + COMMON, + /** + * A collection of fields. + */ + COLLECTION,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTypeEnum.java new file mode 100644 index 0000000..bdfdd75 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/WriteTypeEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.enums; + +/** + * Enumeration of write methods + * + * @author Jiaju Zhuang + **/ +public enum WriteTypeEnum { + /** + * Add. + */ + ADD, + /** + * Fill. + */ + FILL,; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/BorderStyleEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/BorderStyleEnum.java new file mode 100644 index 0000000..ad0f04d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/BorderStyleEnum.java @@ -0,0 +1,95 @@ +package ai.chat2db.excel.enums.poi; + +import lombok.Getter; +import org.apache.poi.ss.usermodel.BorderStyle; + +/** + * The enumeration value indicating the line style of a border in a cell, + * i.e., whether it is bordered dash dot, dash dot dot, dashed, dotted, double, hair, medium, + * medium dash dot, medium dash dot dot, medium dashed, none, slant dash dot, thick or thin. + * + * @author Jiaju Zhuang + */ +@Getter +public enum BorderStyleEnum { + /** + * null + */ + DEFAULT(null), + + /** + * No border (default) + */ + NONE(BorderStyle.NONE), + + /** + * Thin border + */ + THIN(BorderStyle.THIN), + + /** + * Medium border + */ + MEDIUM(BorderStyle.MEDIUM), + + /** + * dash border + */ + DASHED(BorderStyle.DASHED), + + /** + * dot border + */ + DOTTED(BorderStyle.DOTTED), + + /** + * Thick border + */ + THICK(BorderStyle.THICK), + + /** + * double-line border + */ + DOUBLE(BorderStyle.DOUBLE), + + /** + * hair-line border + */ + HAIR(BorderStyle.HAIR), + + /** + * Medium dashed border + */ + MEDIUM_DASHED(BorderStyle.MEDIUM_DASHED), + + /** + * dash-dot border + */ + DASH_DOT(BorderStyle.DASH_DOT), + + /** + * medium dash-dot border + */ + MEDIUM_DASH_DOT(BorderStyle.MEDIUM_DASH_DOT), + + /** + * dash-dot-dot border + */ + DASH_DOT_DOT(BorderStyle.DASH_DOT_DOT), + + /** + * medium dash-dot-dot border + */ + MEDIUM_DASH_DOT_DOT(BorderStyle.MEDIUM_DASH_DOT_DOT), + + /** + * slanted dash-dot border + */ + SLANTED_DASH_DOT(BorderStyle.SLANTED_DASH_DOT); + + BorderStyle poiBorderStyle; + + BorderStyleEnum(BorderStyle poiBorderStyle) { + this.poiBorderStyle = poiBorderStyle; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/FillPatternTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/FillPatternTypeEnum.java new file mode 100644 index 0000000..789cdbd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/FillPatternTypeEnum.java @@ -0,0 +1,119 @@ +package ai.chat2db.excel.enums.poi; + +import lombok.Getter; +import org.apache.poi.ss.usermodel.FillPatternType; + +/** + * The enumeration value indicating the style of fill pattern being used for a cell format. + * + * @author Jiaju Zhuang + */ +@Getter +public enum FillPatternTypeEnum { + + /** + * null + */ + DEFAULT(null), + + /** + * No background + */ + NO_FILL(FillPatternType.NO_FILL), + + /** + * Solidly filled + */ + SOLID_FOREGROUND(FillPatternType.SOLID_FOREGROUND), + + /** + * Small fine dots + */ + FINE_DOTS(FillPatternType.FINE_DOTS), + + /** + * Wide dots + */ + ALT_BARS(FillPatternType.ALT_BARS), + + /** + * Sparse dots + */ + SPARSE_DOTS(FillPatternType.SPARSE_DOTS), + + /** + * Thick horizontal bands + */ + THICK_HORZ_BANDS(FillPatternType.THICK_HORZ_BANDS), + + /** + * Thick vertical bands + */ + THICK_VERT_BANDS(FillPatternType.THICK_VERT_BANDS), + + /** + * Thick backward facing diagonals + */ + THICK_BACKWARD_DIAG(FillPatternType.THICK_BACKWARD_DIAG), + + /** + * Thick forward facing diagonals + */ + THICK_FORWARD_DIAG(FillPatternType.THICK_FORWARD_DIAG), + + /** + * Large spots + */ + BIG_SPOTS(FillPatternType.BIG_SPOTS), + + /** + * Brick-like layout + */ + BRICKS(FillPatternType.BRICKS), + + /** + * Thin horizontal bands + */ + THIN_HORZ_BANDS(FillPatternType.THIN_HORZ_BANDS), + + /** + * Thin vertical bands + */ + THIN_VERT_BANDS(FillPatternType.THIN_VERT_BANDS), + + /** + * Thin backward diagonal + */ + THIN_BACKWARD_DIAG(FillPatternType.THIN_BACKWARD_DIAG), + + /** + * Thin forward diagonal + */ + THIN_FORWARD_DIAG(FillPatternType.THIN_FORWARD_DIAG), + + /** + * Squares + */ + SQUARES(FillPatternType.SQUARES), + + /** + * Diamonds + */ + DIAMONDS(FillPatternType.DIAMONDS), + + /** + * Less Dots + */ + LESS_DOTS(FillPatternType.LESS_DOTS), + + /** + * Least Dots + */ + LEAST_DOTS(FillPatternType.LEAST_DOTS); + + FillPatternType poiFillPatternType; + + FillPatternTypeEnum(FillPatternType poiFillPatternType) { + this.poiFillPatternType = poiFillPatternType; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/HorizontalAlignmentEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/HorizontalAlignmentEnum.java new file mode 100644 index 0000000..4b5418c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/HorizontalAlignmentEnum.java @@ -0,0 +1,93 @@ +package ai.chat2db.excel.enums.poi; + +import lombok.Getter; +import org.apache.poi.ss.usermodel.HorizontalAlignment; + +/** + * The enumeration value indicating horizontal alignment of a cell, + * i.e., whether it is aligned general, left, right, horizontally centered, filled (replicated), + * justified, centered across multiple cells, or distributed. + * @author Jiaju Zhuang + */ +@Getter +public enum HorizontalAlignmentEnum { + /** + * null + */ + DEFAULT(null), + /** + * The horizontal alignment is general-aligned. Text data is left-aligned. + * Numbers, dates, and times are rightaligned. Boolean types are centered. + * Changing the alignment does not change the type of data. + */ + GENERAL(HorizontalAlignment.GENERAL), + + /** + * The horizontal alignment is left-aligned, even in Rightto-Left mode. + * Aligns contents at the left edge of the cell. If an indent amount is specified, the contents of + * the cell is indented from the left by the specified number of character spaces. The character spaces are + * based on the default font and font size for the workbook. + */ + LEFT(HorizontalAlignment.LEFT), + + /** + * The horizontal alignment is centered, meaning the text is centered across the cell. + */ + CENTER(HorizontalAlignment.CENTER), + + /** + * The horizontal alignment is right-aligned, meaning that cell contents are aligned at the right edge of the cell, + * even in Right-to-Left mode. + */ + RIGHT(HorizontalAlignment.RIGHT), + + /** + * Indicates that the value of the cell should be filled + * across the entire width of the cell. If blank cells to the right also have the fill alignment, + * they are also filled with the value, using a convention similar to centerContinuous. + * + * Additional rules: + *
    + *
  1. Only whole values can be appended, not partial values.
  2. + *
  3. The column will not be widened to 'best fit' the filled value
  4. + *
  5. If appending an additional occurrence of the value exceeds the boundary of the cell + * left/right edge, don't append the additional occurrence of the value.
  6. + *
  7. The display value of the cell is filled, not the underlying raw number.
  8. + *
+ */ + FILL(HorizontalAlignment.FILL), + + /** + * The horizontal alignment is justified (flush left and right). + * For each line of text, aligns each line of the wrapped text in a cell to the right and left + * (except the last line). If no single line of text wraps in the cell, then the text is not justified. + */ + JUSTIFY(HorizontalAlignment.JUSTIFY), + + /** + * The horizontal alignment is centered across multiple cells. + * The information about how many cells to span is expressed in the Sheet Part, + * in the row of the cell in question. For each cell that is spanned in the alignment, + * a cell element needs to be written out, with the same style Id which references the centerContinuous alignment. + */ + CENTER_SELECTION(HorizontalAlignment.CENTER_SELECTION), + + /** + * Indicates that each 'word' in each line of text inside the cell is evenly distributed + * across the width of the cell, with flush right and left margins. + *

+ * When there is also an indent value to apply, both the left and right side of the cell + * are padded by the indent value. + *

+ *

A 'word' is a set of characters with no space character in them.

+ *

Two lines inside a cell are separated by a carriage return.

+ */ + DISTRIBUTED(HorizontalAlignment.DISTRIBUTED); + + HorizontalAlignment poiHorizontalAlignment; + + HorizontalAlignmentEnum(HorizontalAlignment poiHorizontalAlignment) { + this.poiHorizontalAlignment = poiHorizontalAlignment; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/VerticalAlignmentEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/VerticalAlignmentEnum.java new file mode 100644 index 0000000..4ddeea8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/enums/poi/VerticalAlignmentEnum.java @@ -0,0 +1,71 @@ +package ai.chat2db.excel.enums.poi; + +import lombok.Getter; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * This enumeration value indicates the type of vertical alignment for a cell, i.e., + * whether it is aligned top, bottom, vertically centered, justified or distributed. + * + * + * + * @author Jiaju Zhuang + */ +@Getter +public enum VerticalAlignmentEnum { + /** + * null + */ + DEFAULT(null), + /** + * The vertical alignment is aligned-to-top. + */ + TOP(VerticalAlignment.TOP), + + /** + * The vertical alignment is centered across the height of the cell. + */ + CENTER(VerticalAlignment.CENTER), + + /** + * The vertical alignment is aligned-to-bottom. (typically the default value) + */ + BOTTOM(VerticalAlignment.BOTTOM), + + /** + *

+ * When text direction is horizontal: the vertical alignment of lines of text is distributed vertically, + * where each line of text inside the cell is evenly distributed across the height of the cell, + * with flush top and bottom margins. + *

+ *

+ * When text direction is vertical: similar behavior as horizontal justification. + * The alignment is justified (flush top and bottom in this case). For each line of text, each + * line of the wrapped text in a cell is aligned to the top and bottom (except the last line). + * If no single line of text wraps in the cell, then the text is not justified. + *

+ */ + JUSTIFY(VerticalAlignment.JUSTIFY), + + /** + *

+ * When text direction is horizontal: the vertical alignment of lines of text is distributed vertically, + * where each line of text inside the cell is evenly distributed across the height of the cell, + * with flush top + *

+ *

+ * When text direction is vertical: behaves exactly as distributed horizontal alignment. + * The first words in a line of text (appearing at the top of the cell) are flush + * with the top edge of the cell, and the last words of a line of text are flush with the bottom edge of the cell, + * and the line of text is distributed evenly from top to bottom. + *

+ */ + DISTRIBUTED(VerticalAlignment.DISTRIBUTED); + + VerticalAlignment poiVerticalAlignmentEnum; + + VerticalAlignmentEnum(VerticalAlignment poiVerticalAlignmentEnum) { + this.poiVerticalAlignmentEnum = poiVerticalAlignmentEnum; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AbstractIgnoreExceptionReadListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AbstractIgnoreExceptionReadListener.java new file mode 100644 index 0000000..3e9b2f3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AbstractIgnoreExceptionReadListener.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.event; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.read.listener.ReadListener; + +/** + * Receives the return of each piece of data parsed + * + * @author jipengfei + * @deprecated Use directly {@link ReadListener} + */ +@Deprecated +public abstract class AbstractIgnoreExceptionReadListener implements ReadListener { + + /** + * All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the + * entire read will terminate. + * + * @param exception + * @param context + */ + @Override + public void onException(Exception exception, AnalysisContext context) {} + + /** + * The current method is called when extra information is returned + * + * @param extra extra information + * @param context analysis context + */ + @Override + public void extra(CellExtra extra, AnalysisContext context) {} + + @Override + public boolean hasNext(AnalysisContext context) { + return true; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AnalysisEventListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AnalysisEventListener.java new file mode 100644 index 0000000..015c7d3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/AnalysisEventListener.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.event; + +import java.util.Map; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ConverterUtils; + +/** + * Receives the return of each piece of data parsed + * + * @author jipengfei + */ +public abstract class AnalysisEventListener implements ReadListener { + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context); + } + + /** + * Returns the header as a map.Override the current method to receive header data. + * + * @param headMap + * @param context + */ + public void invokeHeadMap(Map headMap, AnalysisContext context) {} + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Handler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Handler.java new file mode 100644 index 0000000..1e0049e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Handler.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.event; + +import ai.chat2db.excel.constant.OrderConstant; + +/** + * Intercepts handle some business logic + * + * @author Jiaju Zhuang + **/ +public interface Handler extends Order { + + /** + * handler order + * + * @return order + */ + @Override + default int order() { + return OrderConstant.DEFAULT_ORDER; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Listener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Listener.java new file mode 100644 index 0000000..34be780 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Listener.java @@ -0,0 +1,8 @@ +package ai.chat2db.excel.event; + +/** + * Interface to listen for processing results + * + * @author Jiaju Zhuang + */ +public interface Listener {} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/NotRepeatExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/NotRepeatExecutor.java new file mode 100644 index 0000000..9b8523e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/NotRepeatExecutor.java @@ -0,0 +1,16 @@ +package ai.chat2db.excel.event; + +/** + * There are multiple interceptors that execute only one of them when fired once.If you want to control which one to + * execute please use {@link Order} + * + * @author Jiaju Zhuang + **/ +public interface NotRepeatExecutor { + /** + * To see if it's the same executor + * + * @return + */ + String uniqueValue(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Order.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Order.java new file mode 100644 index 0000000..2b6d576 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/Order.java @@ -0,0 +1,15 @@ +package ai.chat2db.excel.event; + +/** + * Implement this interface when sorting + * + * @author Jiaju Zhuang + */ +public interface Order { + /** + * The smaller the first implementation + * + * @return + */ + int order(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/SyncReadListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/SyncReadListener.java new file mode 100644 index 0000000..0f73f2a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/event/SyncReadListener.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.event; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * Synchronous data reading + * + * @author Jiaju Zhuang + */ +public class SyncReadListener extends AnalysisEventListener { + private List list = new ArrayList(); + + @Override + public void invoke(Object object, AnalysisContext context) { + list.add(object); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisException.java new file mode 100644 index 0000000..e66728b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisException.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.exception; + +/** + * + * @author jipengfei + */ +public class ExcelAnalysisException extends ExcelRuntimeException { + + public ExcelAnalysisException() {} + + public ExcelAnalysisException(String message) { + super(message); + } + + public ExcelAnalysisException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelAnalysisException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopException.java new file mode 100644 index 0000000..00b3fac --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopException.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.exception; + +/** + * Throw the exception when you need to stop + * This exception will stop the entire excel parsing. If you only want to stop the parsing of a certain sheet, please + * use ExcelAnalysisStopSheetException. + * + * @author Jiaju Zhuang + * @see ExcelAnalysisStopException + */ +public class ExcelAnalysisStopException extends ExcelAnalysisException { + + public ExcelAnalysisStopException() {} + + public ExcelAnalysisStopException(String message) { + super(message); + } + + public ExcelAnalysisStopException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelAnalysisStopException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopSheetException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopSheetException.java new file mode 100644 index 0000000..ab0469c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelAnalysisStopSheetException.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.exception; + +/** + * Throw the exception when you need to stop + * This exception will only stop the parsing of the current sheet. If you want to stop the entire excel parsing, please + * use ExcelAnalysisStopException. + * + * The com.alibaba.excel.read.listener.ReadListener#doAfterAllAnalysed(com.alibaba.excel.context.AnalysisContext) method + * is called after the call is stopped. + * + * @author Jiaju Zhuang + * @see ExcelAnalysisStopException + * @since 3.3.4 + */ +public class ExcelAnalysisStopSheetException extends ExcelAnalysisException { + + public ExcelAnalysisStopSheetException() {} + + public ExcelAnalysisStopSheetException(String message) { + super(message); + } + + public ExcelAnalysisStopSheetException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelAnalysisStopSheetException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelCommonException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelCommonException.java new file mode 100644 index 0000000..f521833 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelCommonException.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.exception; + +/** + * + * @author Jiaju Zhuang + */ +public class ExcelCommonException extends ExcelRuntimeException { + + public ExcelCommonException() {} + + public ExcelCommonException(String message) { + super(message); + } + + public ExcelCommonException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelCommonException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelDataConvertException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelDataConvertException.java new file mode 100644 index 0000000..82624a9 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelDataConvertException.java @@ -0,0 +1,57 @@ +package ai.chat2db.excel.exception; + +import ai.chat2db.excel.write.builder.ExcelWriterBuilder; +import ai.chat2db.excel.metadata.data.CellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Data convert exception + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcelDataConvertException extends ExcelRuntimeException { + /** + * NotNull. + */ + private Integer rowIndex; + /** + * NotNull. + */ + private Integer columnIndex; + /** + * NotNull. + */ + private CellData cellData; + /** + * Nullable.Only when the header is configured and when the class header is used is not null. + * + * @see ExcelWriterBuilder#head(Class) + */ + private ExcelContentProperty excelContentProperty; + + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message) { + super(message); + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + this.cellData = cellData; + this.excelContentProperty = excelContentProperty; + } + + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message, Throwable cause) { + super(message, cause); + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + this.cellData = cellData; + this.excelContentProperty = excelContentProperty; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelGenerateException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelGenerateException.java new file mode 100644 index 0000000..89adc02 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelGenerateException.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.exception; + +/** + * @author jipengfei + */ +public class ExcelGenerateException extends ExcelRuntimeException { + + public ExcelGenerateException(String message) { + super(message); + } + + public ExcelGenerateException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelGenerateException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelRuntimeException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelRuntimeException.java new file mode 100644 index 0000000..dd7d2ae --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelRuntimeException.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.exception; + +/** + * Excel Exception + * @author Jiaju Zhuang + */ +public class ExcelRuntimeException extends RuntimeException { + public ExcelRuntimeException() {} + + public ExcelRuntimeException(String message) { + super(message); + } + + public ExcelRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public ExcelRuntimeException(Throwable cause) { + super(cause); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelWriteDataConvertException.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelWriteDataConvertException.java new file mode 100644 index 0000000..cb0ad46 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/exception/ExcelWriteDataConvertException.java @@ -0,0 +1,36 @@ +package ai.chat2db.excel.exception; + +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Data convert exception + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcelWriteDataConvertException extends ExcelDataConvertException { + /** + * handler. + */ + private CellWriteHandlerContext cellWriteHandlerContext; + + public ExcelWriteDataConvertException(CellWriteHandlerContext cellWriteHandlerContext, String message) { + super(cellWriteHandlerContext.getRowIndex(), cellWriteHandlerContext.getColumnIndex(), + cellWriteHandlerContext.getFirstCellData(), cellWriteHandlerContext.getExcelContentProperty(), message); + this.cellWriteHandlerContext = cellWriteHandlerContext; + } + + public ExcelWriteDataConvertException(CellWriteHandlerContext cellWriteHandlerContext, String message, + Throwable cause) { + super(cellWriteHandlerContext.getRowIndex(), cellWriteHandlerContext.getColumnIndex(), + cellWriteHandlerContext.getFirstCellData(), cellWriteHandlerContext.getExcelContentProperty(), message, + cause); + this.cellWriteHandlerContext = cellWriteHandlerContext; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractCell.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractCell.java new file mode 100644 index 0000000..f1a0a0b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractCell.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.metadata; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * cell + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class AbstractCell implements Cell { + /** + * Row index + */ + private Integer rowIndex; + /** + * Column index + */ + private Integer columnIndex; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractHolder.java new file mode 100644 index 0000000..a633400 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractHolder.java @@ -0,0 +1,112 @@ +package ai.chat2db.excel.metadata; + +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Write/read holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public abstract class AbstractHolder implements ConfigurationHolder { + /** + * Record whether it's new or from cache + */ + private Boolean newInitialization; + /** + * You can only choose one of the {@link AbstractHolder#head} and {@link AbstractHolder#clazz} + */ + private List> head; + /** + * You can only choose one of the {@link AbstractHolder#head} and {@link AbstractHolder#clazz} + */ + private Class clazz; + /** + * Some global variables + */ + private GlobalConfiguration globalConfiguration; + /** + *

+ * Read key: + *

+ * Write key: + */ + private Map> converterMap; + + public AbstractHolder(BasicParameter basicParameter, AbstractHolder prentAbstractHolder) { + this.newInitialization = Boolean.TRUE; + if (basicParameter.getHead() == null && basicParameter.getClazz() == null && prentAbstractHolder != null) { + this.head = prentAbstractHolder.getHead(); + } else { + this.head = basicParameter.getHead(); + } + if (basicParameter.getHead() == null && basicParameter.getClazz() == null && prentAbstractHolder != null) { + this.clazz = prentAbstractHolder.getClazz(); + } else { + this.clazz = basicParameter.getClazz(); + } + this.globalConfiguration = new GlobalConfiguration(); + if (basicParameter.getAutoTrim() == null) { + if (prentAbstractHolder != null) { + globalConfiguration.setAutoTrim(prentAbstractHolder.getGlobalConfiguration().getAutoTrim()); + } + } else { + globalConfiguration.setAutoTrim(basicParameter.getAutoTrim()); + } + + if (basicParameter.getUse1904windowing() == null) { + if (prentAbstractHolder != null) { + globalConfiguration.setUse1904windowing( + prentAbstractHolder.getGlobalConfiguration().getUse1904windowing()); + } + } else { + globalConfiguration.setUse1904windowing(basicParameter.getUse1904windowing()); + } + + if (basicParameter.getLocale() == null) { + if (prentAbstractHolder != null) { + globalConfiguration.setLocale(prentAbstractHolder.getGlobalConfiguration().getLocale()); + } + } else { + globalConfiguration.setLocale(basicParameter.getLocale()); + } + + if (basicParameter.getFiledCacheLocation() == null) { + if (prentAbstractHolder != null) { + globalConfiguration.setFiledCacheLocation( + prentAbstractHolder.getGlobalConfiguration().getFiledCacheLocation()); + } + } else { + globalConfiguration.setFiledCacheLocation(basicParameter.getFiledCacheLocation()); + } + + } + + @Override + public Map> converterMap() { + return getConverterMap(); + } + + @Override + public GlobalConfiguration globalConfiguration() { + return getGlobalConfiguration(); + } + + @Override + public boolean isNew() { + return getNewInitialization(); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractParameterBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractParameterBuilder.java new file mode 100644 index 0000000..a970489 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/AbstractParameterBuilder.java @@ -0,0 +1,111 @@ +package ai.chat2db.excel.metadata; + +import java.util.List; +import java.util.Locale; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CacheLocationEnum; +import ai.chat2db.excel.util.ListUtils; + +/** + * ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractParameterBuilder { + /** + * You can only choose one of the {@link #head(List)} and {@link #head(Class)} + * + * @param head + * @return + */ + public T head(List> head) { + parameter().setHead(head); + return self(); + } + + /** + * You can only choose one of the {@link #head(List)} and {@link #head(Class)} + * + * @param clazz + * @return + */ + public T head(Class clazz) { + parameter().setClazz(clazz); + return self(); + } + + /** + * Custom type conversions override the default. + * + * @param converter + * @return + */ + public T registerConverter(Converter converter) { + if (parameter().getCustomConverterList() == null) { + parameter().setCustomConverterList(ListUtils.newArrayList()); + } + parameter().getCustomConverterList().add(converter); + return self(); + } + + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @param use1904windowing + * @return + */ + public T use1904windowing(Boolean use1904windowing) { + parameter().setUse1904windowing(use1904windowing); + return self(); + } + + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + * + * @param locale + * @return + */ + public T locale(Locale locale) { + parameter().setLocale(locale); + return self(); + } + + /** + * The cache used when parsing fields such as head. + * + * default is THREAD_LOCAL. + * + * @since 3.3.0 + */ + public T filedCacheLocation(CacheLocationEnum filedCacheLocation) { + parameter().setFiledCacheLocation(filedCacheLocation); + return self(); + } + + /** + * Automatic trim includes sheet name and content + * + * @param autoTrim + * @return + */ + public T autoTrim(Boolean autoTrim) { + parameter().setAutoTrim(autoTrim); + return self(); + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T)this; + } + + /** + * Get parameter + * + * @return + */ + protected abstract C parameter(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/BasicParameter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/BasicParameter.java new file mode 100644 index 0000000..105d48d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/BasicParameter.java @@ -0,0 +1,65 @@ +package ai.chat2db.excel.metadata; + +import java.util.List; +import java.util.Locale; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.enums.CacheLocationEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Basic parameter + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class BasicParameter { + /** + * You can only choose one of the {@link BasicParameter#head} and {@link BasicParameter#clazz} + */ + private List> head; + /** + * You can only choose one of the {@link BasicParameter#head} and {@link BasicParameter#clazz} + */ + private Class clazz; + /** + * Custom type conversions override the default + */ + private List> customConverterList; + /** + * Automatic trim includes sheet name and content + */ + private Boolean autoTrim; + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @return + */ + private Boolean use1904windowing; + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + */ + private Locale locale; + + /** + * Whether to use scientific Format. + * + * default is false + */ + private Boolean useScientificFormat; + + /** + * The cache used when parsing fields such as head. + * + * default is THREAD_LOCAL. + */ + private CacheLocationEnum filedCacheLocation; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Cell.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Cell.java new file mode 100644 index 0000000..dc1a64b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Cell.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.metadata; + +/** + * Cell + * + * @author Jiaju Zhuang + **/ +public interface Cell { + /** + * Row index + * + * @return + */ + Integer getRowIndex(); + + /** + * Column index + * + * @return + */ + Integer getColumnIndex(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellExtra.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellExtra.java new file mode 100644 index 0000000..8adcc95 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellExtra.java @@ -0,0 +1,121 @@ +package ai.chat2db.excel.metadata; + +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import org.apache.poi.ss.util.CellReference; + +import ai.chat2db.excel.constant.ExcelXmlConstants; + +/** + * Cell extra information. + * + * @author Jiaju Zhuang + */ +public class CellExtra extends AbstractCell { + /** + * Cell extra type + */ + private CellExtraTypeEnum type; + /** + * Cell extra data + */ + private String text; + /** + * First row index, if this object is an interval + */ + private Integer firstRowIndex; + /** + * Last row index, if this object is an interval + */ + private Integer lastRowIndex; + /** + * First column index, if this object is an interval + */ + private Integer firstColumnIndex; + /** + * Last column index, if this object is an interval + */ + private Integer lastColumnIndex; + + public CellExtra(CellExtraTypeEnum type, String text, String range) { + super(); + this.type = type; + this.text = text; + String[] ranges = range.split(ExcelXmlConstants.CELL_RANGE_SPLIT); + CellReference first = new CellReference(ranges[0]); + CellReference last = first; + this.firstRowIndex = first.getRow(); + this.firstColumnIndex = (int)first.getCol(); + setRowIndex(this.firstRowIndex); + setColumnIndex(this.firstColumnIndex); + if (ranges.length > 1) { + last = new CellReference(ranges[1]); + } + this.lastRowIndex = last.getRow(); + this.lastColumnIndex = (int)last.getCol(); + } + + public CellExtra(CellExtraTypeEnum type, String text, Integer rowIndex, Integer columnIndex) { + this(type, text, rowIndex, rowIndex, columnIndex, columnIndex); + } + + public CellExtra(CellExtraTypeEnum type, String text, Integer firstRowIndex, Integer lastRowIndex, + Integer firstColumnIndex, Integer lastColumnIndex) { + super(); + setRowIndex(firstRowIndex); + setColumnIndex(firstColumnIndex); + this.type = type; + this.text = text; + this.firstRowIndex = firstRowIndex; + this.firstColumnIndex = firstColumnIndex; + this.lastRowIndex = lastRowIndex; + this.lastColumnIndex = lastColumnIndex; + } + + public CellExtraTypeEnum getType() { + return type; + } + + public void setType(CellExtraTypeEnum type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Integer getFirstRowIndex() { + return firstRowIndex; + } + + public void setFirstRowIndex(Integer firstRowIndex) { + this.firstRowIndex = firstRowIndex; + } + + public Integer getFirstColumnIndex() { + return firstColumnIndex; + } + + public void setFirstColumnIndex(Integer firstColumnIndex) { + this.firstColumnIndex = firstColumnIndex; + } + + public Integer getLastRowIndex() { + return lastRowIndex; + } + + public void setLastRowIndex(Integer lastRowIndex) { + this.lastRowIndex = lastRowIndex; + } + + public Integer getLastColumnIndex() { + return lastColumnIndex; + } + + public void setLastColumnIndex(Integer lastColumnIndex) { + this.lastColumnIndex = lastColumnIndex; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellRange.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellRange.java new file mode 100644 index 0000000..027c79b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/CellRange.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.metadata; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellRange { + + private int firstRow; + private int lastRow; + private int firstCol; + private int lastCol; + + public CellRange(int firstRow, int lastRow, int firstCol, int lastCol) { + this.firstRow = firstRow; + this.lastRow = lastRow; + this.firstCol = firstCol; + this.lastCol = lastCol; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/ConfigurationHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/ConfigurationHolder.java new file mode 100644 index 0000000..bcb54ac --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/ConfigurationHolder.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.metadata; + +import java.util.Map; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; + +/** + * Get the corresponding holder + * + * @author Jiaju Zhuang + **/ +public interface ConfigurationHolder extends Holder { + + /** + * Record whether it's new or from cache + * + * @return Record whether it's new or from cache + */ + boolean isNew(); + + /** + * Some global variables + * + * @return Global configuration + */ + GlobalConfiguration globalConfiguration(); + + /** + * What converter does the currently operated cell need to execute + * + * @return Converter + */ + Map> converterMap(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldCache.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldCache.java new file mode 100644 index 0000000..0aa6c5e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldCache.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.metadata; + +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * filed cache + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +public class FieldCache { + + /** + * A field cache that has been sorted by a class. + * It will exclude fields that are not needed. + */ + private Map sortedFieldMap; + + /** + * Fields using the index attribute + */ + private Map indexFieldMap; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldWrapper.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldWrapper.java new file mode 100644 index 0000000..89f7707 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/FieldWrapper.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.metadata; + +import java.lang.reflect.Field; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * filed wrapper + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor +public class FieldWrapper { + + /** + * field + */ + private Field field; + + /** + * The field name matching cglib + */ + private String fieldName; + + /** + * The name of the sheet header. + * + * @see ExcelProperty + */ + private String[] heads; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Font.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Font.java new file mode 100644 index 0000000..86f8acb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Font.java @@ -0,0 +1,48 @@ +package ai.chat2db.excel.metadata; + +import ai.chat2db.excel.write.metadata.style.WriteFont; + +/** + * + * @author jipengfei + * @deprecated please use {@link WriteFont} + */ +@Deprecated +public class Font { + + /** + */ + private String fontName; + + /** + */ + private short fontHeightInPoints; + + /** + */ + private boolean bold; + + public String getFontName() { + return fontName; + } + + public void setFontName(String fontName) { + this.fontName = fontName; + } + + public short getFontHeightInPoints() { + return fontHeightInPoints; + } + + public void setFontHeightInPoints(short fontHeightInPoints) { + this.fontHeightInPoints = fontHeightInPoints; + } + + public boolean isBold() { + return bold; + } + + public void setBold(boolean bold) { + this.bold = bold; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/GlobalConfiguration.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/GlobalConfiguration.java new file mode 100644 index 0000000..ab86fe6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/GlobalConfiguration.java @@ -0,0 +1,59 @@ +package ai.chat2db.excel.metadata; + +import java.util.Locale; + +import ai.chat2db.excel.enums.CacheLocationEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Global configuration + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class GlobalConfiguration { + /** + * Automatic trim includes sheet name and content + */ + private Boolean autoTrim; + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @return + */ + private Boolean use1904windowing; + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + */ + private Locale locale; + + /** + * Whether to use scientific Format. + * + * default is false + */ + private Boolean useScientificFormat; + + /** + * The cache used when parsing fields such as head. + * + * default is THREAD_LOCAL. + */ + private CacheLocationEnum filedCacheLocation; + + public GlobalConfiguration() { + this.autoTrim = Boolean.TRUE; + this.use1904windowing = Boolean.FALSE; + this.locale = Locale.getDefault(); + this.useScientificFormat = Boolean.FALSE; + this.filedCacheLocation = CacheLocationEnum.THREAD_LOCAL; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Head.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Head.java new file mode 100644 index 0000000..cdfeed2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Head.java @@ -0,0 +1,87 @@ +package ai.chat2db.excel.metadata; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.metadata.property.ColumnWidthProperty; +import ai.chat2db.excel.metadata.property.FontProperty; +import ai.chat2db.excel.metadata.property.LoopMergeProperty; +import ai.chat2db.excel.metadata.property.StyleProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * excel head + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class Head { + /** + * Column index of head + */ + private Integer columnIndex; + /** + * It only has values when passed in {@link Sheet#setClazz(Class)} and {@link Table#setClazz(Class)} + */ + private Field field; + /** + * It only has values when passed in {@link Sheet#setClazz(Class)} and {@link Table#setClazz(Class)} + */ + private String fieldName; + /** + * Head name + */ + private List headNameList; + /** + * Whether index is specified + */ + private Boolean forceIndex; + /** + * Whether to specify a name + */ + private Boolean forceName; + + /** + * column with + */ + private ColumnWidthProperty columnWidthProperty; + + /** + * Loop merge + */ + private LoopMergeProperty loopMergeProperty; + /** + * Head style + */ + private StyleProperty headStyleProperty; + /** + * Head font + */ + private FontProperty headFontProperty; + + public Head(Integer columnIndex, Field field, String fieldName, List headNameList, Boolean forceIndex, + Boolean forceName) { + this.columnIndex = columnIndex; + this.field = field; + this.fieldName = fieldName; + if (headNameList == null) { + this.headNameList = new ArrayList<>(); + } else { + this.headNameList = headNameList; + for (String headName : headNameList) { + if (headName == null) { + throw new ExcelGenerateException("head name can not be null."); + } + } + } + this.forceIndex = forceIndex; + this.forceName = forceName; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Holder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Holder.java new file mode 100644 index 0000000..33b4ab5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/Holder.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.metadata; + +import ai.chat2db.excel.enums.HolderEnum; + +/** + * + * Get the corresponding holder + * + * @author Jiaju Zhuang + **/ +public interface Holder { + + /** + * What holder is the return + * + * @return Holder enum. + */ + HolderEnum holderType(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/NullObject.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/NullObject.java new file mode 100644 index 0000000..6aa39dd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/NullObject.java @@ -0,0 +1,9 @@ +package ai.chat2db.excel.metadata; + +/** + * Null object. + * + * @author Jiaju Zhuang + */ +public class NullObject { +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCell.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCell.java new file mode 100644 index 0000000..196603f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCell.java @@ -0,0 +1,351 @@ +package ai.chat2db.excel.metadata.csv; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Date; + +import ai.chat2db.excel.enums.NumericCellTypeEnum; +import ai.chat2db.excel.metadata.data.FormulaData; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.CellBase; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; + +/** + * csv cell + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvCell extends CellBase { + + /** + * column index + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private Integer columnIndex; + + /** + * cell type + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private CellType cellType; + + /** + * numeric cell type + */ + private NumericCellTypeEnum numericCellType; + + /** + * workbook + */ + private final CsvWorkbook csvWorkbook; + + /** + * sheet + */ + private final CsvSheet csvSheet; + + /** + * row + */ + private final CsvRow csvRow; + + /** + * {@link CellType#NUMERIC} + */ + private BigDecimal numberValue; + /** + * {@link CellType#STRING} and {@link CellType#ERROR} {@link CellType#FORMULA} + */ + private String stringValue; + /** + * {@link CellType#BOOLEAN} + */ + private Boolean booleanValue; + + /** + * {@link CellType#NUMERIC} + */ + private LocalDateTime dateValue; + + /** + * formula + */ + private FormulaData formulaData; + + /** + * rich text string + */ + private RichTextString richTextString; + + /** + * style + */ + private CellStyle cellStyle; + + public CsvCell(CsvWorkbook csvWorkbook, CsvSheet csvSheet, CsvRow csvRow, Integer columnIndex, CellType cellType) { + this.csvWorkbook = csvWorkbook; + this.csvSheet = csvSheet; + this.csvRow = csvRow; + this.columnIndex = columnIndex; + this.cellType = cellType; + if (this.cellType == null) { + this.cellType = CellType._NONE; + } + } + + @Override + protected void setCellTypeImpl(CellType cellType) { + this.cellType = cellType; + } + + @Override + protected void setCellFormulaImpl(String formula) { + FormulaData formulaData = new FormulaData(); + formulaData.setFormulaValue(formula); + this.formulaData = formulaData; + this.cellType = CellType.FORMULA; + } + + @Override + protected void removeFormulaImpl() { + this.formulaData = null; + } + + @Override + protected void setCellValueImpl(double value) { + numberValue = BigDecimal.valueOf(value); + this.cellType = CellType.NUMERIC; + } + + @Override + protected void setCellValueImpl(Date value) { + if (value == null) { + return; + } + this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); + this.cellType = CellType.NUMERIC; + this.numericCellType = NumericCellTypeEnum.DATE; + } + + @Override + protected void setCellValueImpl(LocalDateTime value) { + this.dateValue = value; + this.cellType = CellType.NUMERIC; + this.numericCellType = NumericCellTypeEnum.DATE; + } + + @Override + protected void setCellValueImpl(Calendar value) { + if (value == null) { + return; + } + this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); + this.cellType = CellType.NUMERIC; + } + + @Override + protected void setCellValueImpl(String value) { + this.stringValue = value; + this.cellType = CellType.STRING; + } + + @Override + protected void setCellValueImpl(RichTextString value) { + richTextString = value; + this.cellType = CellType.STRING; + } + + @Override + public void setCellValue(String value) { + if (value == null) { + setBlank(); + return; + } + setCellValueImpl(value); + } + + @Override + public void setCellValue(RichTextString value) { + if (value == null || value.getString() == null) { + setBlank(); + return; + } + setCellValueImpl(value); + } + + @Override + protected SpreadsheetVersion getSpreadsheetVersion() { + return null; + } + + @Override + public int getColumnIndex() { + return columnIndex; + } + + @Override + public int getRowIndex() { + return csvRow.getRowNum(); + } + + @Override + public Sheet getSheet() { + return csvRow.getSheet(); + } + + @Override + public Row getRow() { + return csvRow; + } + + @Override + public CellType getCellType() { + return cellType; + } + + @Override + public CellType getCachedFormulaResultType() { + return getCellType(); + } + + @Override + public String getCellFormula() { + if (formulaData == null) { + return null; + } + return formulaData.getFormulaValue(); + } + + @Override + public double getNumericCellValue() { + if (numberValue == null) { + return 0; + } + return numberValue.doubleValue(); + } + + @Override + public Date getDateCellValue() { + if (dateValue == null) { + return null; + } + return Date.from(dateValue.atZone(ZoneId.systemDefault()).toInstant()); + } + + @Override + public LocalDateTime getLocalDateTimeCellValue() { + return dateValue; + } + + @Override + public RichTextString getRichStringCellValue() { + return richTextString; + } + + @Override + public String getStringCellValue() { + return stringValue; + } + + @Override + public void setCellValue(boolean value) { + this.booleanValue = value; + this.cellType = CellType.BOOLEAN; + } + + @Override + public void setCellErrorValue(byte value) { + this.numberValue = BigDecimal.valueOf(value); + this.cellType = CellType.ERROR; + } + + @Override + public boolean getBooleanCellValue() { + if (booleanValue == null) { + return false; + } + return booleanValue; + } + + @Override + public byte getErrorCellValue() { + if (numberValue == null) { + return 0; + } + return numberValue.byteValue(); + } + + @Override + public void setCellStyle(CellStyle style) { + this.cellStyle = style; + } + + @Override + public CellStyle getCellStyle() { + return cellStyle; + } + + @Override + public void setAsActiveCell() { + + } + + @Override + public void setCellComment(Comment comment) { + + } + + @Override + public Comment getCellComment() { + return null; + } + + @Override + public void removeCellComment() { + + } + + @Override + public Hyperlink getHyperlink() { + return null; + } + + @Override + public void setHyperlink(Hyperlink link) { + + } + + @Override + public void removeHyperlink() { + + } + + @Override + public CellRangeAddress getArrayFormulaRange() { + return null; + } + + @Override + public boolean isPartOfArrayFormulaGroup() { + return false; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCellStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCellStyle.java new file mode 100644 index 0000000..e82e418 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvCellStyle.java @@ -0,0 +1,312 @@ +package ai.chat2db.excel.metadata.csv; + +import ai.chat2db.excel.metadata.data.DataFormatData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Color; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * csv cell style + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvCellStyle implements CellStyle { + + /** + * data format + */ + private DataFormatData dataFormatData; + + /** + * index + */ + private Short index; + + public CsvCellStyle(Short index) { + this.index = index; + } + + @Override + public short getIndex() { + return index; + } + + @Override + public void setDataFormat(short fmt) { + initDataFormatData(); + dataFormatData.setIndex(fmt); + } + + private void initDataFormatData() { + if (dataFormatData == null) { + dataFormatData = new DataFormatData(); + } + } + + @Override + public short getDataFormat() { + if (dataFormatData == null) { + return 0; + } + return dataFormatData.getIndex(); + } + + @Override + public String getDataFormatString() { + if (dataFormatData == null) { + return null; + } + return dataFormatData.getFormat(); + } + + @Override + public void setFont(Font font) { + + } + + @Override + public int getFontIndex() { + return 0; + } + + @Override + public int getFontIndexAsInt() { + return 0; + } + + @Override + public void setHidden(boolean hidden) { + + } + + @Override + public boolean getHidden() { + return false; + } + + @Override + public void setLocked(boolean locked) { + + } + + @Override + public boolean getLocked() { + return false; + } + + @Override + public void setQuotePrefixed(boolean quotePrefix) { + + } + + @Override + public boolean getQuotePrefixed() { + return false; + } + + @Override + public void setAlignment(HorizontalAlignment align) { + + } + + @Override + public HorizontalAlignment getAlignment() { + return null; + } + + @Override + public void setWrapText(boolean wrapped) { + + } + + @Override + public boolean getWrapText() { + return false; + } + + @Override + public void setVerticalAlignment(VerticalAlignment align) { + + } + + @Override + public VerticalAlignment getVerticalAlignment() { + return null; + } + + @Override + public void setRotation(short rotation) { + + } + + @Override + public short getRotation() { + return 0; + } + + @Override + public void setIndention(short indent) { + + } + + @Override + public short getIndention() { + return 0; + } + + @Override + public void setBorderLeft(BorderStyle border) { + + } + + @Override + public BorderStyle getBorderLeft() { + return null; + } + + @Override + public void setBorderRight(BorderStyle border) { + + } + + @Override + public BorderStyle getBorderRight() { + return null; + } + + @Override + public void setBorderTop(BorderStyle border) { + + } + + @Override + public BorderStyle getBorderTop() { + return null; + } + + @Override + public void setBorderBottom(BorderStyle border) { + + } + + @Override + public BorderStyle getBorderBottom() { + return null; + } + + @Override + public void setLeftBorderColor(short color) { + + } + + @Override + public short getLeftBorderColor() { + return 0; + } + + @Override + public void setRightBorderColor(short color) { + + } + + @Override + public short getRightBorderColor() { + return 0; + } + + @Override + public void setTopBorderColor(short color) { + + } + + @Override + public short getTopBorderColor() { + return 0; + } + + @Override + public void setBottomBorderColor(short color) { + + } + + @Override + public short getBottomBorderColor() { + return 0; + } + + @Override + public void setFillPattern(FillPatternType fp) { + + } + + @Override + public FillPatternType getFillPattern() { + return null; + } + + @Override + public void setFillBackgroundColor(short bg) { + + } + + @Override + public void setFillBackgroundColor(Color color) { + + } + + @Override + public short getFillBackgroundColor() { + return 0; + } + + @Override + public Color getFillBackgroundColorColor() { + return null; + } + + @Override + public void setFillForegroundColor(short bg) { + + } + + @Override + public void setFillForegroundColor(Color color) { + + } + + @Override + public short getFillForegroundColor() { + return 0; + } + + @Override + public Color getFillForegroundColorColor() { + return null; + } + + @Override + public void cloneStyleFrom(CellStyle source) { + + } + + @Override + public void setShrinkToFit(boolean shrinkToFit) { + + } + + @Override + public boolean getShrinkToFit() { + return false; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvDataFormat.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvDataFormat.java new file mode 100644 index 0000000..050808c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvDataFormat.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.metadata.csv; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.constant.BuiltinFormats; + +import org.apache.poi.ss.usermodel.DataFormat; + +/** + * format data + * + * @author Jiaju Zhuang + */ +public class CsvDataFormat implements DataFormat { + /** + * It is stored in both map and list for easy retrieval + */ + private final Map formatMap; + private final List formatList; + + /** + * Excel's built-in format conversion. + */ + private final Map builtinFormatsMap; + private final String[] builtinFormats; + + public CsvDataFormat(Locale locale) { + formatMap = MapUtils.newHashMap(); + formatList = ListUtils.newArrayList(); + builtinFormatsMap = BuiltinFormats.switchBuiltinFormatsMap(locale); + builtinFormats = BuiltinFormats.switchBuiltinFormats(locale); + } + + @Override + public short getFormat(String format) { + Short index = builtinFormatsMap.get(format); + if (index != null) { + return index; + } + index = formatMap.get(format); + if (index != null) { + return index; + } + short indexPrimitive = (short)(formatList.size() + BuiltinFormats.MIN_CUSTOM_DATA_FORMAT_INDEX); + index = indexPrimitive; + formatList.add(format); + formatMap.put(format, index); + return indexPrimitive; + } + + @Override + public String getFormat(short index) { + if (index < BuiltinFormats.MIN_CUSTOM_DATA_FORMAT_INDEX) { + return builtinFormats[index]; + } + int actualIndex = index - BuiltinFormats.MIN_CUSTOM_DATA_FORMAT_INDEX; + if (actualIndex < formatList.size()) { + return formatList.get(actualIndex); + } + return null; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRichTextString.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRichTextString.java new file mode 100644 index 0000000..5027af6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRichTextString.java @@ -0,0 +1,68 @@ +package ai.chat2db.excel.metadata.csv; + +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.RichTextString; + +/** + * rich text string + * + * @author Jiaju Zhuang + */ +public class CsvRichTextString implements RichTextString { + /** + * string + */ + private final String string; + + public CsvRichTextString(String string) { + this.string = string; + } + + @Override + public void applyFont(int startIndex, int endIndex, short fontIndex) { + + } + + @Override + public void applyFont(int startIndex, int endIndex, Font font) { + + } + + @Override + public void applyFont(Font font) { + + } + + @Override + public void clearFormatting() { + + } + + @Override + public String getString() { + return string; + } + + @Override + public int length() { + if (string == null) { + return 0; + } + return string.length(); + } + + @Override + public int numFormattingRuns() { + return 0; + } + + @Override + public int getIndexOfFormattingRun(int index) { + return 0; + } + + @Override + public void applyFont(short fontIndex) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRow.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRow.java new file mode 100644 index 0000000..1ae7b2f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvRow.java @@ -0,0 +1,196 @@ +package ai.chat2db.excel.metadata.csv; + +import java.util.Iterator; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.compress.utils.Lists; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * csv row + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvRow implements Row { + + /** + * cell list + */ + private final List cellList; + + /** + * workbook + */ + private final CsvWorkbook csvWorkbook; + + /** + * sheet + */ + private final CsvSheet csvSheet; + + /** + * row index + */ + private Integer rowIndex; + + /** + * style + */ + private CellStyle cellStyle; + + public CsvRow(CsvWorkbook csvWorkbook, CsvSheet csvSheet, Integer rowIndex) { + cellList = Lists.newArrayList(); + this.csvWorkbook = csvWorkbook; + this.csvSheet = csvSheet; + this.rowIndex = rowIndex; + } + + @Override + public Cell createCell(int column) { + CsvCell cell = new CsvCell(csvWorkbook, csvSheet, this, column, null); + cellList.add(cell); + return cell; + } + + @Override + public Cell createCell(int column, CellType type) { + CsvCell cell = new CsvCell(csvWorkbook, csvSheet, this, column, type); + cellList.add(cell); + return cell; + } + + @Override + public void removeCell(Cell cell) { + cellList.remove(cell); + } + + @Override + public void setRowNum(int rowNum) { + this.rowIndex = rowNum; + } + + @Override + public int getRowNum() { + return rowIndex; + } + + @Override + public Cell getCell(int cellnum) { + if (cellnum >= cellList.size()) { + return null; + } + return cellList.get(cellnum - 1); + } + + @Override + public Cell getCell(int cellnum, MissingCellPolicy policy) { + return getCell(cellnum); + } + + @Override + public short getFirstCellNum() { + if (CollectionUtils.isEmpty(cellList)) { + return -1; + } + return 0; + } + + @Override + public short getLastCellNum() { + if (CollectionUtils.isEmpty(cellList)) { + return -1; + } + return (short)cellList.size(); + } + + @Override + public int getPhysicalNumberOfCells() { + return getRowNum(); + } + + @Override + public void setHeight(short height) { + + } + + @Override + public void setZeroHeight(boolean zHeight) { + + } + + @Override + public boolean getZeroHeight() { + return false; + } + + @Override + public void setHeightInPoints(float height) { + + } + + @Override + public short getHeight() { + return 0; + } + + @Override + public float getHeightInPoints() { + return 0; + } + + @Override + public boolean isFormatted() { + return false; + } + + @Override + public CellStyle getRowStyle() { + return cellStyle; + } + + @Override + public void setRowStyle(CellStyle style) { + this.cellStyle = style; + } + + @Override + public Iterator cellIterator() { + return (Iterator)(Iterator)cellList.iterator(); + } + + @Override + public Sheet getSheet() { + return csvSheet; + } + + @Override + public int getOutlineLevel() { + return 0; + } + + @Override + public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { + + } + + @Override + public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { + + } + + @Override + public Iterator iterator() { + return cellIterator(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvSheet.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvSheet.java new file mode 100644 index 0000000..989654a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvSheet.java @@ -0,0 +1,831 @@ +package ai.chat2db.excel.metadata.csv; + +import java.io.Closeable; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.ByteOrderMarkEnum; +import ai.chat2db.excel.enums.NumericCellTypeEnum; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.NumberDataFormatterUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.constant.BuiltinFormats; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.poi.ss.usermodel.AutoFilter; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellRange; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Footer; +import org.apache.poi.ss.usermodel.Header; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.PageMargin; +import org.apache.poi.ss.usermodel.PaneType; +import org.apache.poi.ss.usermodel.PrintSetup; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.SheetConditionalFormatting; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellAddress; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.PaneInformation; + +/** + * csv sheet + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvSheet implements Sheet, Closeable { + + /** + * workbook + */ + private CsvWorkbook csvWorkbook; + /** + * output + */ + private Appendable out; + /** + * row cache + */ + private Integer rowCacheCount; + /** + * format + */ + public CSVFormat csvFormat; + + /** + * last row index + */ + private Integer lastRowIndex; + + /** + * row cache + */ + private List rowCache; + /** + * csv printer + */ + private CSVPrinter csvPrinter; + + public CsvSheet(CsvWorkbook csvWorkbook, Appendable out) { + this.csvWorkbook = csvWorkbook; + this.out = out; + this.rowCacheCount = 100; + this.csvFormat = CSVFormat.DEFAULT; + this.lastRowIndex = -1; + } + + @Override + public Row createRow(int rownum) { + // Initialize the data when the row is first created + initSheet(); + + lastRowIndex++; + assert rownum == lastRowIndex : "csv create row must be in order."; + printData(); + CsvRow csvRow = new CsvRow(csvWorkbook, this, rownum); + rowCache.add(csvRow); + return csvRow; + } + + private void initSheet() { + if (csvPrinter != null) { + return; + } + rowCache = ListUtils.newArrayListWithExpectedSize(rowCacheCount); + try { + if (csvWorkbook.getWithBom()) { + ByteOrderMarkEnum byteOrderMark = ByteOrderMarkEnum.valueOfByCharsetName( + csvWorkbook.getCharset().name()); + if (byteOrderMark != null) { + out.append(byteOrderMark.getStringPrefix()); + } + } + csvPrinter = csvFormat.print(out); + } catch (IOException e) { + throw new ExcelGenerateException(e); + } + } + + @Override + public void removeRow(Row row) { + throw new UnsupportedOperationException("csv cannot move row."); + } + + @Override + public Row getRow(int rownum) { + int actualRowIndex = rownum - (lastRowIndex - rowCache.size()) - 1; + if (actualRowIndex < 0 || actualRowIndex > rowCache.size() - 1) { + throw new UnsupportedOperationException("The current data does not exist or has been flushed to disk\n."); + } + return rowCache.get(actualRowIndex); + } + + @Override + public int getPhysicalNumberOfRows() { + return lastRowIndex - rowCache.size(); + } + + @Override + public int getFirstRowNum() { + if (lastRowIndex < 0) { + return -1; + } + return 0; + } + + @Override + public int getLastRowNum() { + return lastRowIndex; + } + + @Override + public void setColumnHidden(int columnIndex, boolean hidden) { + + } + + @Override + public boolean isColumnHidden(int columnIndex) { + return false; + } + + @Override + public void setRightToLeft(boolean value) { + + } + + @Override + public boolean isRightToLeft() { + return false; + } + + @Override + public void setColumnWidth(int columnIndex, int width) { + + } + + @Override + public int getColumnWidth(int columnIndex) { + return 0; + } + + @Override + public float getColumnWidthInPixels(int columnIndex) { + return 0; + } + + @Override + public void setDefaultColumnWidth(int width) { + + } + + @Override + public int getDefaultColumnWidth() { + return 0; + } + + @Override + public short getDefaultRowHeight() { + return 0; + } + + @Override + public float getDefaultRowHeightInPoints() { + return 0; + } + + @Override + public void setDefaultRowHeight(short height) { + + } + + @Override + public void setDefaultRowHeightInPoints(float height) { + + } + + @Override + public CellStyle getColumnStyle(int column) { + return null; + } + + @Override + public int addMergedRegion(CellRangeAddress region) { + return 0; + } + + @Override + public int addMergedRegionUnsafe(CellRangeAddress region) { + return 0; + } + + @Override + public void validateMergedRegions() { + + } + + @Override + public void setVerticallyCenter(boolean value) { + + } + + @Override + public void setHorizontallyCenter(boolean value) { + + } + + @Override + public boolean getHorizontallyCenter() { + return false; + } + + @Override + public boolean getVerticallyCenter() { + return false; + } + + @Override + public void removeMergedRegion(int index) { + + } + + @Override + public void removeMergedRegions(Collection indices) { + + } + + @Override + public int getNumMergedRegions() { + return 0; + } + + @Override + public CellRangeAddress getMergedRegion(int index) { + return null; + } + + @Override + public List getMergedRegions() { + return null; + } + + @Override + public Iterator rowIterator() { + return (Iterator)(Iterator)rowCache.iterator(); + } + + @Override + public void setForceFormulaRecalculation(boolean value) { + + } + + @Override + public boolean getForceFormulaRecalculation() { + return false; + } + + @Override + public void setAutobreaks(boolean value) { + + } + + @Override + public void setDisplayGuts(boolean value) { + + } + + @Override + public void setDisplayZeros(boolean value) { + + } + + @Override + public boolean isDisplayZeros() { + return false; + } + + @Override + public void setFitToPage(boolean value) { + + } + + @Override + public void setRowSumsBelow(boolean value) { + + } + + @Override + public void setRowSumsRight(boolean value) { + + } + + @Override + public boolean getAutobreaks() { + return false; + } + + @Override + public boolean getDisplayGuts() { + return false; + } + + @Override + public boolean getFitToPage() { + return false; + } + + @Override + public boolean getRowSumsBelow() { + return false; + } + + @Override + public boolean getRowSumsRight() { + return false; + } + + @Override + public boolean isPrintGridlines() { + return false; + } + + @Override + public void setPrintGridlines(boolean show) { + + } + + @Override + public boolean isPrintRowAndColumnHeadings() { + return false; + } + + @Override + public void setPrintRowAndColumnHeadings(boolean show) { + + } + + @Override + public PrintSetup getPrintSetup() { + return null; + } + + @Override + public Header getHeader() { + return null; + } + + @Override + public Footer getFooter() { + return null; + } + + @Override + public void setSelected(boolean value) { + + } + + @Override + public double getMargin(short margin) { + return 0; + } + + @Override + public double getMargin(PageMargin pageMargin) { + return 0; + } + + @Override + public void setMargin(short margin, double size) { + + } + + @Override + public void setMargin(PageMargin pageMargin, double v) { + + } + + @Override + public boolean getProtect() { + return false; + } + + @Override + public void protectSheet(String password) { + + } + + @Override + public boolean getScenarioProtect() { + return false; + } + + @Override + public void setZoom(int scale) { + + } + + @Override + public short getTopRow() { + return 0; + } + + @Override + public short getLeftCol() { + return 0; + } + + @Override + public void showInPane(int topRow, int leftCol) { + + } + + @Override + public void shiftRows(int startRow, int endRow, int n) { + + } + + @Override + public void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { + + } + + @Override + public void shiftColumns(int startColumn, int endColumn, int n) { + + } + + @Override + public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) { + + } + + @Override + public void createFreezePane(int colSplit, int rowSplit) { + + } + + @Override + public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) { + + } + + @Override + public void createSplitPane(int i, int i1, int i2, int i3, PaneType paneType) { + + } + + @Override + public PaneInformation getPaneInformation() { + return null; + } + + @Override + public void setDisplayGridlines(boolean show) { + + } + + @Override + public boolean isDisplayGridlines() { + return false; + } + + @Override + public void setDisplayFormulas(boolean show) { + + } + + @Override + public boolean isDisplayFormulas() { + return false; + } + + @Override + public void setDisplayRowColHeadings(boolean show) { + + } + + @Override + public boolean isDisplayRowColHeadings() { + return false; + } + + @Override + public void setRowBreak(int row) { + + } + + @Override + public boolean isRowBroken(int row) { + return false; + } + + @Override + public void removeRowBreak(int row) { + + } + + @Override + public int[] getRowBreaks() { + return new int[0]; + } + + @Override + public int[] getColumnBreaks() { + return new int[0]; + } + + @Override + public void setColumnBreak(int column) { + + } + + @Override + public boolean isColumnBroken(int column) { + return false; + } + + @Override + public void removeColumnBreak(int column) { + + } + + @Override + public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) { + + } + + @Override + public void groupColumn(int fromColumn, int toColumn) { + + } + + @Override + public void ungroupColumn(int fromColumn, int toColumn) { + + } + + @Override + public void groupRow(int fromRow, int toRow) { + + } + + @Override + public void ungroupRow(int fromRow, int toRow) { + + } + + @Override + public void setRowGroupCollapsed(int row, boolean collapse) { + + } + + @Override + public void setDefaultColumnStyle(int column, CellStyle style) { + + } + + @Override + public void autoSizeColumn(int column) { + + } + + @Override + public void autoSizeColumn(int column, boolean useMergedCells) { + + } + + @Override + public Comment getCellComment(CellAddress ref) { + return null; + } + + @Override + public Map getCellComments() { + return null; + } + + @Override + public Drawing getDrawingPatriarch() { + return null; + } + + @Override + public Drawing createDrawingPatriarch() { + return null; + } + + @Override + public Workbook getWorkbook() { + return csvWorkbook; + } + + @Override + public String getSheetName() { + return null; + } + + @Override + public boolean isSelected() { + return false; + } + + @Override + public CellRange setArrayFormula( + String formula, CellRangeAddress range) { + return null; + } + + @Override + public CellRange removeArrayFormula(Cell cell) { + return null; + } + + @Override + public DataValidationHelper getDataValidationHelper() { + return null; + } + + @Override + public List getDataValidations() { + return null; + } + + @Override + public void addValidationData(DataValidation dataValidation) { + + } + + @Override + public AutoFilter setAutoFilter(CellRangeAddress range) { + return null; + } + + @Override + public SheetConditionalFormatting getSheetConditionalFormatting() { + return null; + } + + @Override + public CellRangeAddress getRepeatingRows() { + return null; + } + + @Override + public CellRangeAddress getRepeatingColumns() { + return null; + } + + @Override + public void setRepeatingRows(CellRangeAddress rowRangeRef) { + + } + + @Override + public void setRepeatingColumns(CellRangeAddress columnRangeRef) { + + } + + @Override + public int getColumnOutlineLevel(int columnIndex) { + return 0; + } + + @Override + public Hyperlink getHyperlink(int row, int column) { + return null; + } + + @Override + public Hyperlink getHyperlink(CellAddress addr) { + return null; + } + + @Override + public List getHyperlinkList() { + return null; + } + + @Override + public CellAddress getActiveCell() { + return null; + } + + @Override + public void setActiveCell(CellAddress address) { + + } + + @Override + public Iterator iterator() { + return rowIterator(); + } + + @Override + public void close() throws IOException { + // Avoid empty sheets + initSheet(); + + flushData(); + csvPrinter.flush(); + csvPrinter.close(); + } + + public void printData() { + if (rowCache.size() >= rowCacheCount) { + flushData(); + } + } + + public void flushData() { + try { + for (CsvRow row : rowCache) { + Iterator cellIterator = row.cellIterator(); + int columnIndex = 0; + while (cellIterator.hasNext()) { + CsvCell csvCell = (CsvCell)cellIterator.next(); + while (csvCell.getColumnIndex() > columnIndex++) { + csvPrinter.print(null); + } + csvPrinter.print(buildCellValue(csvCell)); + } + csvPrinter.println(); + } + rowCache.clear(); + } catch (IOException e) { + throw new ExcelGenerateException(e); + } + } + + private String buildCellValue(CsvCell csvCell) { + switch (csvCell.getCellType()) { + case STRING: + case ERROR: + return csvCell.getStringCellValue(); + case NUMERIC: + Short dataFormat = null; + String dataFormatString = null; + if (csvCell.getCellStyle() != null) { + dataFormat = csvCell.getCellStyle().getDataFormat(); + dataFormatString = csvCell.getCellStyle().getDataFormatString(); + } + if (csvCell.getNumericCellType() == NumericCellTypeEnum.DATE) { + if (csvCell.getDateValue() == null) { + return null; + } + // date + if (dataFormat == null) { + dataFormatString = DateUtils.defaultDateFormat; + dataFormat = csvWorkbook.createDataFormat().getFormat(dataFormatString); + } + if (dataFormatString == null) { + dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat); + } + return NumberDataFormatterUtils.format(BigDecimal.valueOf( + DateUtil.getExcelDate(csvCell.getDateValue(), csvWorkbook.getUse1904windowing())), + dataFormat, dataFormatString, csvWorkbook.getUse1904windowing(), csvWorkbook.getLocale(), + csvWorkbook.getUseScientificFormat()); + } else { + if (csvCell.getNumberValue() == null) { + return null; + } + //number + if (dataFormat == null) { + dataFormat = BuiltinFormats.GENERAL; + dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat); + } + if (dataFormatString == null) { + dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat); + } + return NumberDataFormatterUtils.format(csvCell.getNumberValue(), dataFormat, dataFormatString, + csvWorkbook.getUse1904windowing(), csvWorkbook.getLocale(), + csvWorkbook.getUseScientificFormat()); + } + case BOOLEAN: + return csvCell.getBooleanValue().toString(); + case BLANK: + return StringUtils.EMPTY; + default: + return null; + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvWorkbook.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvWorkbook.java new file mode 100644 index 0000000..98d9349 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/csv/CsvWorkbook.java @@ -0,0 +1,419 @@ +package ai.chat2db.excel.metadata.csv; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.compress.utils.Lists; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.formula.udf.UDFFinder; +import org.apache.poi.ss.usermodel.CellReferenceType; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.SheetVisibility; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * csv workbook + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvWorkbook implements Workbook { + /** + * output + */ + private Appendable out; + + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + *

+ * default is false + * + */ + private Boolean use1904windowing; + + /** + * locale + */ + private Locale locale; + + /** + * Whether to use scientific Format. + *

+ * default is false + */ + private Boolean useScientificFormat; + + /** + * data format + */ + private CsvDataFormat csvDataFormat; + /** + * sheet + */ + private CsvSheet csvSheet; + /** + * cell style + */ + private List csvCellStyleList; + + /** + * charset. + */ + private Charset charset; + + /** + * Set the encoding prefix in the csv file, otherwise the office may open garbled characters. + * Default true. + */ + private Boolean withBom; + + public CsvWorkbook(Appendable out, Locale locale, Boolean use1904windowing, Boolean useScientificFormat, + Charset charset, Boolean withBom) { + this.out = out; + this.locale = locale; + this.use1904windowing = use1904windowing; + this.useScientificFormat = useScientificFormat; + this.charset = charset; + this.withBom = withBom; + } + + @Override + public int getActiveSheetIndex() { + return 0; + } + + @Override + public void setActiveSheet(int sheetIndex) { + + } + + @Override + public int getFirstVisibleTab() { + return 0; + } + + @Override + public void setFirstVisibleTab(int sheetIndex) { + + } + + @Override + public void setSheetOrder(String sheetname, int pos) { + + } + + @Override + public void setSelectedTab(int index) { + + } + + @Override + public void setSheetName(int sheet, String name) { + + } + + @Override + public String getSheetName(int sheet) { + return null; + } + + @Override + public int getSheetIndex(String name) { + return 0; + } + + @Override + public int getSheetIndex(Sheet sheet) { + return 0; + } + + @Override + public Sheet createSheet() { + assert csvSheet == null : "CSV repeat creation is not allowed."; + csvSheet = new CsvSheet(this, out); + return csvSheet; + } + + @Override + public Sheet createSheet(String sheetname) { + assert csvSheet == null : "CSV repeat creation is not allowed."; + csvSheet = new CsvSheet(this, out); + return csvSheet; + } + + @Override + public Sheet cloneSheet(int sheetNum) { + return null; + } + + @Override + public Iterator sheetIterator() { + return null; + } + + @Override + public int getNumberOfSheets() { + return 0; + } + + @Override + public Sheet getSheetAt(int index) { + assert index == 0 : "CSV exists only in one sheet."; + return csvSheet; + } + + @Override + public Sheet getSheet(String name) { + return csvSheet; + } + + @Override + public void removeSheetAt(int index) { + + } + + @Override + public Font createFont() { + return null; + } + + @Override + public Font findFont(boolean bold, short color, short fontHeight, String name, boolean italic, boolean strikeout, + short typeOffset, byte underline) { + return null; + } + + @Override + public int getNumberOfFonts() { + return 0; + } + + @Override + public int getNumberOfFontsAsInt() { + return 0; + } + + @Override + public Font getFontAt(int idx) { + return null; + } + + @Override + public CellStyle createCellStyle() { + if (csvCellStyleList == null) { + csvCellStyleList = Lists.newArrayList(); + } + CsvCellStyle csvCellStyle = new CsvCellStyle((short)csvCellStyleList.size()); + csvCellStyleList.add(csvCellStyle); + return csvCellStyle; + } + + @Override + public int getNumCellStyles() { + return csvCellStyleList.size(); + } + + @Override + public CellStyle getCellStyleAt(int idx) { + if (idx < 0 || idx >= csvCellStyleList.size()) { + return null; + } + return csvCellStyleList.get(idx); + } + + @Override + public void write(OutputStream stream) throws IOException { + csvSheet.close(); + } + + @Override + public void close() throws IOException { + + } + + @Override + public int getNumberOfNames() { + return 0; + } + + @Override + public Name getName(String name) { + return null; + } + + @Override + public List getNames(String name) { + return null; + } + + @Override + public List getAllNames() { + return null; + } + + @Override + public Name createName() { + return null; + } + + @Override + public void removeName(Name name) { + + } + + @Override + public int linkExternalWorkbook(String name, Workbook workbook) { + return 0; + } + + @Override + public void setPrintArea(int sheetIndex, String reference) { + + } + + @Override + public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { + + } + + @Override + public String getPrintArea(int sheetIndex) { + return null; + } + + @Override + public void removePrintArea(int sheetIndex) { + + } + + @Override + public MissingCellPolicy getMissingCellPolicy() { + return null; + } + + @Override + public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) { + + } + + @Override + public DataFormat createDataFormat() { + if (csvDataFormat != null) { + return csvDataFormat; + } + csvDataFormat = new CsvDataFormat(locale); + return csvDataFormat; + } + + @Override + public int addPicture(byte[] pictureData, int format) { + return 0; + } + + @Override + public List getAllPictures() { + return null; + } + + @Override + public CreationHelper getCreationHelper() { + return null; + } + + @Override + public boolean isHidden() { + return false; + } + + @Override + public void setHidden(boolean hiddenFlag) { + + } + + @Override + public boolean isSheetHidden(int sheetIx) { + return false; + } + + @Override + public boolean isSheetVeryHidden(int sheetIx) { + return false; + } + + @Override + public void setSheetHidden(int sheetIx, boolean hidden) { + + } + + @Override + public SheetVisibility getSheetVisibility(int sheetIx) { + return null; + } + + @Override + public void setSheetVisibility(int sheetIx, SheetVisibility visibility) { + + } + + @Override + public void addToolPack(UDFFinder toopack) { + + } + + @Override + public void setForceFormulaRecalculation(boolean value) { + + } + + @Override + public boolean getForceFormulaRecalculation() { + return false; + } + + @Override + public SpreadsheetVersion getSpreadsheetVersion() { + return null; + } + + @Override + public int addOlePackage(byte[] oleData, String label, String fileName, String command) { + return 0; + } + + @Override + public EvaluationWorkbook createEvaluationWorkbook() { + return null; + } + + @Override + public CellReferenceType getCellReferenceType() { + return null; + } + + @Override + public void setCellReferenceType(CellReferenceType cellReferenceType) { + + } + + @Override + public Iterator iterator() { + return null; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CellData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CellData.java new file mode 100644 index 0000000..b464279 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CellData.java @@ -0,0 +1,80 @@ +package ai.chat2db.excel.metadata.data; + +import java.math.BigDecimal; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.AbstractCell; +import ai.chat2db.excel.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Excel internal cell data. + * + *

+ * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellData extends AbstractCell { + /** + * cell type + */ + private CellDataTypeEnum type; + /** + * {@link CellDataTypeEnum#NUMBER} + */ + private BigDecimal numberValue; + /** + * {@link CellDataTypeEnum#STRING} and{@link CellDataTypeEnum#ERROR} + */ + private String stringValue; + /** + * {@link CellDataTypeEnum#BOOLEAN} + */ + private Boolean booleanValue; + + /** + * The resulting converted data. + */ + private T data; + + /** + * formula + */ + private FormulaData formulaData; + + /** + * Ensure that the object does not appear null + */ + public void checkEmpty() { + if (type == null) { + type = CellDataTypeEnum.EMPTY; + } + switch (type) { + case STRING: + case DIRECT_STRING: + case ERROR: + if (StringUtils.isEmpty(stringValue)) { + type = CellDataTypeEnum.EMPTY; + } + return; + case NUMBER: + if (numberValue == null) { + type = CellDataTypeEnum.EMPTY; + } + return; + case BOOLEAN: + if (booleanValue == null) { + type = CellDataTypeEnum.EMPTY; + } + return; + default: + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ClientAnchorData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ClientAnchorData.java new file mode 100644 index 0000000..23b87dc --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ClientAnchorData.java @@ -0,0 +1,123 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.util.Internal; + +/** + * A client anchor is attached to an excel worksheet. It anchors against + * absolute coordinates, a top-left cell and fixed height and width, or + * a top-left and bottom-right cell, depending on the {@link ClientAnchorData.AnchorType}: + *

    + *
  1. {@link ClientAnchor.AnchorType#DONT_MOVE_AND_RESIZE} == absolute top-left coordinates and width/height, no + * cell references + *
  2. {@link ClientAnchor.AnchorType#MOVE_DONT_RESIZE} == fixed top-left cell reference, absolute width/height + *
  3. {@link ClientAnchor.AnchorType#MOVE_AND_RESIZE} == fixed top-left and bottom-right cell references, dynamic + * width/height + *
+ * Note this class only reports the current values for possibly calculated positions and sizes. + * If the sheet row/column sizes or positions shift, this needs updating via external calculations. + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ClientAnchorData extends CoordinateData { + /** + * top + */ + private Integer top; + + /** + * right + */ + private Integer right; + + /** + * bottom + */ + private Integer bottom; + + /** + * left + */ + private Integer left; + + /** + * anchor type + */ + private AnchorType anchorType; + + @Getter + public enum AnchorType { + /** + * Move and Resize With Anchor Cells (0) + *

+ * Specifies that the current drawing shall move and + * resize to maintain its row and column anchors (i.e. the + * object is anchored to the actual from and to row and column) + *

+ */ + MOVE_AND_RESIZE(ClientAnchor.AnchorType.MOVE_AND_RESIZE), + + /** + * Don't Move but do Resize With Anchor Cells (1) + *

+ * Specifies that the current drawing shall not move with its + * row and column, but should be resized. This option is not normally + * used, but is included for completeness. + *

+ * Note: Excel has no setting for this combination, nor does the ECMA standard. + */ + DONT_MOVE_DO_RESIZE(ClientAnchor.AnchorType.DONT_MOVE_DO_RESIZE), + + /** + * Move With Cells but Do Not Resize (2) + *

+ * Specifies that the current drawing shall move with its + * row and column (i.e. the object is anchored to the + * actual from row and column), but that the size shall remain absolute. + *

+ *

+ * If additional rows/columns are added between the from and to locations of the drawing, + * the drawing shall move its to anchors as needed to maintain this same absolute size. + *

+ */ + MOVE_DONT_RESIZE(ClientAnchor.AnchorType.MOVE_DONT_RESIZE), + + /** + * Do Not Move or Resize With Underlying Rows/Columns (3) + *

+ * Specifies that the current start and end positions shall + * be maintained with respect to the distances from the + * absolute start point of the worksheet. + *

+ *

+ * If additional rows/columns are added before the + * drawing, the drawing shall move its anchors as needed + * to maintain this same absolute position. + *

+ */ + DONT_MOVE_AND_RESIZE(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE); + + ClientAnchor.AnchorType value; + + AnchorType(ClientAnchor.AnchorType value) { + this.value = value; + } + + /** + * return the AnchorType corresponding to the code + * + * @param value the anchor type code + * @return the anchor type enum + */ + @Internal + public static ClientAnchorData.AnchorType byId(int value) { + return values()[value]; + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CommentData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CommentData.java new file mode 100644 index 0000000..60d69f5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CommentData.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * comment + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CommentData extends ClientAnchorData { + /** + * Name of the original comment author + */ + private String author; + /** + * rich text string + */ + private RichTextStringData richTextStringData; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CoordinateData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CoordinateData.java new file mode 100644 index 0000000..ece4e25 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/CoordinateData.java @@ -0,0 +1,49 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * coordinate. + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CoordinateData { + /** + * first row index.Priority is higher than {@link #relativeFirstRowIndex}. + */ + private Integer firstRowIndex; + /** + * first column index.Priority is higher than {@link #relativeFirstColumnIndex}. + */ + private Integer firstColumnIndex; + /** + * last row index.Priority is higher than {@link #relativeLastRowIndex}. + */ + private Integer lastRowIndex; + /** + * last column index.Priority is higher than {@link #relativeLastColumnIndex}. + */ + private Integer lastColumnIndex; + + /** + * relative first row index + */ + private Integer relativeFirstRowIndex; + /** + * relative first column index + */ + private Integer relativeFirstColumnIndex; + /** + * relative last row index + */ + private Integer relativeLastRowIndex; + /** + *relative last column index + */ + private Integer relativeLastColumnIndex; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/DataFormatData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/DataFormatData.java new file mode 100644 index 0000000..5b285f0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/DataFormatData.java @@ -0,0 +1,53 @@ +package ai.chat2db.excel.metadata.data; + +import ai.chat2db.excel.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * data format + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class DataFormatData { + /** + * index + */ + private Short index; + + /** + * format + */ + private String format; + + /** + * The source is not empty merge the data to the target. + * + * @param source source + * @param target target + */ + public static void merge(DataFormatData source, DataFormatData target) { + if (source == null || target == null) { + return; + } + if (source.getIndex() != null) { + target.setIndex(source.getIndex()); + } + if (StringUtils.isNotBlank(source.getFormat())) { + target.setFormat(source.getFormat()); + } + } + + @Override + public DataFormatData clone() { + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex(getIndex()); + dataFormatData.setFormat(getFormat()); + return dataFormatData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/FormulaData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/FormulaData.java new file mode 100644 index 0000000..a8e186c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/FormulaData.java @@ -0,0 +1,27 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * formula + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FormulaData { + /** + * formula + */ + private String formulaValue; + + @Override + public FormulaData clone() { + FormulaData formulaData = new FormulaData(); + formulaData.setFormulaValue(getFormulaValue()); + return formulaData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/HyperlinkData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/HyperlinkData.java new file mode 100644 index 0000000..dd96ba7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/HyperlinkData.java @@ -0,0 +1,58 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * hyperlink + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class HyperlinkData extends CoordinateData { + /** + * Depending on the hyperlink type it can be URL, e-mail, path to a file, etc + */ + private String address; + /** + * hyperlink type + */ + private HyperlinkType hyperlinkType; + + @Getter + public enum HyperlinkType { + /** + * Not a hyperlink + */ + NONE(org.apache.poi.common.usermodel.HyperlinkType.NONE), + + /** + * Link to an existing file or web page + */ + URL(org.apache.poi.common.usermodel.HyperlinkType.URL), + + /** + * Link to a place in this document + */ + DOCUMENT(org.apache.poi.common.usermodel.HyperlinkType.DOCUMENT), + + /** + * Link to an E-mail address + */ + EMAIL(org.apache.poi.common.usermodel.HyperlinkType.EMAIL), + + /** + * Link to a file + */ + FILE(org.apache.poi.common.usermodel.HyperlinkType.FILE); + + org.apache.poi.common.usermodel.HyperlinkType value; + + HyperlinkType(org.apache.poi.common.usermodel.HyperlinkType value) { + this.value = value; + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ImageData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ImageData.java new file mode 100644 index 0000000..5e92c5a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ImageData.java @@ -0,0 +1,63 @@ +package ai.chat2db.excel.metadata.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * image + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ImageData extends ClientAnchorData { + + /** + * image + */ + private byte[] image; + + /** + * image type + */ + private ImageType imageType; + + @Getter + public enum ImageType { + /** + * Extended windows meta file + */ + PICTURE_TYPE_EMF(2), + /** + * Windows Meta File + */ + PICTURE_TYPE_WMF(3), + /** + * Mac PICT format + */ + PICTURE_TYPE_PICT(4), + /** + * JPEG format + */ + PICTURE_TYPE_JPEG(5), + /** + * PNG format + */ + PICTURE_TYPE_PNG(6), + /** + * Device independent bitmap + */ + PICTURE_TYPE_DIB(7), + + ; + + int value; + + ImageType(int value) { + this.value = value; + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ReadCellData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ReadCellData.java new file mode 100644 index 0000000..fda714f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/ReadCellData.java @@ -0,0 +1,161 @@ +package ai.chat2db.excel.metadata.data; + +import java.math.BigDecimal; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.constant.EasyExcelConstants; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * read cell data + *

+ * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class ReadCellData extends CellData { + + /** + * originalNumberValue vs numberValue + *

    + *
  1. + * NUMBER: + * originalNumberValue: Original data and the accuracy of his is 17, but in fact the excel only 15 precision to + * process the data + * numberValue: After correction of the data and the accuracy of his is 15 + * for example, originalNumberValue = `2087.0249999999996` , numberValue = `2087.03` + *
  2. + *
  3. + * DATE: + * originalNumberValue: Storage is a data type double, accurate to milliseconds + * dateValue: Based on double converted to a date format, he will revised date difference, accurate to seconds + * for example, originalNumberValue = `44729.99998836806` ,time is:`2022-06-17 23:59:58.995`, + * But in excel is displayed:` 2022-06-17 23:59:59`, dateValue = `2022-06-17 23:59:59` + *
  4. + *
+ * {@link CellDataTypeEnum#NUMBER} {@link CellDataTypeEnum#DATE} + */ + private BigDecimal originalNumberValue; + + /** + * data format. + */ + private DataFormatData dataFormatData; + + public ReadCellData(CellDataTypeEnum type) { + super(); + if (type == null) { + throw new IllegalArgumentException("Type can not be null"); + } + setType(type); + } + + public ReadCellData(T data) { + super(); + setData(data); + } + + public ReadCellData(String stringValue) { + this(CellDataTypeEnum.STRING, stringValue); + } + + public ReadCellData(CellDataTypeEnum type, String stringValue) { + super(); + if (type != CellDataTypeEnum.STRING && type != CellDataTypeEnum.ERROR) { + throw new IllegalArgumentException("Only support CellDataTypeEnum.STRING and CellDataTypeEnum.ERROR"); + } + if (stringValue == null) { + throw new IllegalArgumentException("StringValue can not be null"); + } + setType(type); + setStringValue(stringValue); + } + + public ReadCellData(BigDecimal numberValue) { + super(); + if (numberValue == null) { + throw new IllegalArgumentException("DoubleValue can not be null"); + } + setType(CellDataTypeEnum.NUMBER); + setNumberValue(numberValue); + } + + public ReadCellData(Boolean booleanValue) { + super(); + if (booleanValue == null) { + throw new IllegalArgumentException("BooleanValue can not be null"); + } + setType(CellDataTypeEnum.BOOLEAN); + setBooleanValue(booleanValue); + } + + public static ReadCellData newEmptyInstance() { + return newEmptyInstance(null, null); + } + + public static ReadCellData newEmptyInstance(Integer rowIndex, Integer columnIndex) { + ReadCellData cellData = new ReadCellData<>(CellDataTypeEnum.EMPTY); + cellData.setRowIndex(rowIndex); + cellData.setColumnIndex(columnIndex); + return cellData; + } + + public static ReadCellData newInstance(Boolean booleanValue) { + return newInstance(booleanValue, null, null); + } + + public static ReadCellData newInstance(Boolean booleanValue, Integer rowIndex, Integer columnIndex) { + ReadCellData cellData = new ReadCellData<>(booleanValue); + cellData.setRowIndex(rowIndex); + cellData.setColumnIndex(columnIndex); + return cellData; + } + + public static ReadCellData newInstance(String stringValue, Integer rowIndex, Integer columnIndex) { + ReadCellData cellData = new ReadCellData<>(stringValue); + cellData.setRowIndex(rowIndex); + cellData.setColumnIndex(columnIndex); + return cellData; + } + + public static ReadCellData newInstance(BigDecimal numberValue, Integer rowIndex, Integer columnIndex) { + ReadCellData cellData = new ReadCellData<>(numberValue); + cellData.setRowIndex(rowIndex); + cellData.setColumnIndex(columnIndex); + return cellData; + } + + public static ReadCellData newInstanceOriginal(BigDecimal numberValue, Integer rowIndex, Integer columnIndex) { + ReadCellData cellData = new ReadCellData<>(numberValue); + cellData.setRowIndex(rowIndex); + cellData.setColumnIndex(columnIndex); + cellData.setOriginalNumberValue(numberValue); + cellData.setNumberValue(numberValue.round(EasyExcelConstants.EXCEL_MATH_CONTEXT)); + return cellData; + } + + @Override + public ReadCellData clone() { + ReadCellData readCellData = new ReadCellData<>(); + readCellData.setType(getType()); + readCellData.setNumberValue(getNumberValue()); + readCellData.setOriginalNumberValue(getOriginalNumberValue()); + readCellData.setStringValue(getStringValue()); + readCellData.setBooleanValue(getBooleanValue()); + readCellData.setData(getData()); + if (getDataFormatData() != null) { + readCellData.setDataFormatData(getDataFormatData().clone()); + } + if (getFormulaData() != null) { + readCellData.setFormulaData(getFormulaData().clone()); + } + return readCellData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/RichTextStringData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/RichTextStringData.java new file mode 100644 index 0000000..dde43ba --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/RichTextStringData.java @@ -0,0 +1,65 @@ +package ai.chat2db.excel.metadata.data; + +import java.util.List; + +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * rich text string + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class RichTextStringData { + private String textString; + private WriteFont writeFont; + private List intervalFontList; + + public RichTextStringData(String textString) { + this.textString = textString; + } + + @Getter + @Setter + @EqualsAndHashCode + @AllArgsConstructor + public static class IntervalFont { + private Integer startIndex; + private Integer endIndex; + private WriteFont writeFont; + } + + /** + * Applies a font to the specified characters of a string. + * + * @param startIndex The start index to apply the font to (inclusive) + * @param endIndex The end index to apply to font to (exclusive) + * @param writeFont The font to use. + */ + public void applyFont(int startIndex, int endIndex, WriteFont writeFont) { + if (intervalFontList == null) { + intervalFontList = ListUtils.newArrayList(); + } + intervalFontList.add(new IntervalFont(startIndex, endIndex, writeFont)); + } + + /** + * Sets the font of the entire string. + * + * @param writeFont The font to use. + */ + public void applyFont(WriteFont writeFont) { + this.writeFont = writeFont; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/WriteCellData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/WriteCellData.java new file mode 100644 index 0000000..ea719f8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/data/WriteCellData.java @@ -0,0 +1,143 @@ +package ai.chat2db.excel.metadata.data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.poi.ss.usermodel.CellStyle; + +/** + * write cell data + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class WriteCellData extends CellData { + /** + * Support only when writing.{@link CellDataTypeEnum#DATE} + */ + private LocalDateTime dateValue; + + /** + * rich text.{@link CellDataTypeEnum#RICH_TEXT_STRING} + */ + private RichTextStringData richTextStringDataValue; + /** + * image + */ + private List imageDataList; + /** + * comment + */ + private CommentData commentData; + /** + * hyper link + */ + private HyperlinkData hyperlinkData; + + /** + * style + */ + private WriteCellStyle writeCellStyle; + + /** + * If originCellStyle is empty, one will be created. + * If both writeCellStyle and originCellStyle exist, copy from writeCellStyle to originCellStyle. + */ + private CellStyle originCellStyle; + + + public WriteCellData(String stringValue) { + this(CellDataTypeEnum.STRING, stringValue); + } + + public WriteCellData(CellDataTypeEnum type) { + super(); + setType(type); + } + + public WriteCellData(CellDataTypeEnum type, String stringValue) { + super(); + if (type != CellDataTypeEnum.STRING && type != CellDataTypeEnum.ERROR) { + throw new IllegalArgumentException("Only support CellDataTypeEnum.STRING and CellDataTypeEnum.ERROR"); + } + if (stringValue == null) { + throw new IllegalArgumentException("StringValue can not be null"); + } + setType(type); + setStringValue(stringValue); + } + + public WriteCellData(BigDecimal numberValue) { + super(); + if (numberValue == null) { + throw new IllegalArgumentException("DoubleValue can not be null"); + } + setType(CellDataTypeEnum.NUMBER); + setNumberValue(numberValue); + } + + public WriteCellData(Boolean booleanValue) { + super(); + if (booleanValue == null) { + throw new IllegalArgumentException("BooleanValue can not be null"); + } + setType(CellDataTypeEnum.BOOLEAN); + setBooleanValue(booleanValue); + } + + public WriteCellData(Date dateValue) { + super(); + if (dateValue == null) { + throw new IllegalArgumentException("DateValue can not be null"); + } + setType(CellDataTypeEnum.DATE); + this.dateValue = LocalDateTime.ofInstant(dateValue.toInstant(), ZoneId.systemDefault()); + } + + public WriteCellData(LocalDateTime dateValue) { + super(); + if (dateValue == null) { + throw new IllegalArgumentException("DateValue can not be null"); + } + setType(CellDataTypeEnum.DATE); + this.dateValue = dateValue; + } + + public WriteCellData(byte[] image) { + super(); + if (image == null) { + throw new IllegalArgumentException("Image can not be null"); + } + setType(CellDataTypeEnum.EMPTY); + this.imageDataList = ListUtils.newArrayList(); + ImageData imageData = new ImageData(); + imageData.setImage(image); + imageDataList.add(imageData); + } + + /** + * Return a style, if is empty, create a new + * + * @return not null. + */ + public WriteCellStyle getOrCreateStyle() { + if (this.writeCellStyle == null) { + this.writeCellStyle = new WriteCellStyle(); + } + return this.writeCellStyle; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/DataFormatter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/DataFormatter.java new file mode 100644 index 0000000..0feef88 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/DataFormatter.java @@ -0,0 +1,873 @@ +/* + * ==================================================================== Licensed to the Apache Software Foundation (ASF) + * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff + * can be found in svn at location root/projects/3rd-party/src + * ==================================================================== + */ +package ai.chat2db.excel.metadata.format; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ai.chat2db.excel.util.DateUtils; + +import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.FractionFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Written with reference to {@link org.apache.poi.ss.usermodel.DataFormatter}.Made some optimizations for date + * conversion. + *

+ * This is a non-thread-safe class. + * + * @author Jiaju Zhuang + */ +public class DataFormatter { + /** + * For logging any problems we find + */ + private static final Logger LOGGER = LoggerFactory.getLogger(DataFormatter.class); + private static final String defaultFractionWholePartFormat = "#"; + private static final String defaultFractionFractionPartFormat = "#/##"; + /** + * Pattern to find a number format: "0" or "#" + */ + private static final Pattern numPattern = Pattern.compile("[0#]+"); + + /** + * Pattern to find days of week as text "ddd...." + */ + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + + /** + * Pattern to find "AM/PM" marker + */ + private static final Pattern amPmPattern = + Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE); + + /** + * Pattern to find formats with condition ranges e.g. [>=100] + */ + private static final Pattern rangeConditionalPattern = + Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); + + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into + * locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+])"); + + /** + * A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red, + * White, Yellow, "Color n" (1<=n<=56) + */ + private static final Pattern colorPattern = Pattern.compile( + "(\\[BLACK])|(\\[BLUE])|(\\[CYAN])|(\\[GREEN])|" + "(\\[MAGENTA])|(\\[RED])|(\\[WHITE])|(\\[YELLOW])|" + + "(\\[COLOR\\s*\\d])|(\\[COLOR\\s*[0-5]\\d])|(\\[DBNum(1|2|3)])|(\\[\\$-\\d{0,3}])", + Pattern.CASE_INSENSITIVE); + + /** + * A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called + */ + private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*/\\s*([#\\d]+)"); + + /** + * A regex to strip junk out of fraction formats + */ + private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ ?#\\d/]+)"); + + /** + * A regex to detect if an alternate grouping character is used in a numeric format + */ + private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); + + private static final Pattern E_NOTATION_PATTERN = Pattern.compile("E(\\d)"); + + /** + * Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs + * ("#"). + */ + private static final String invalidDateTimeString; + + static { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 255; i++) {buf.append('#');} + invalidDateTimeString = buf.toString(); + } + + /** + * The decimal symbols of the locale used for formatting values. + */ + private DecimalFormatSymbols decimalSymbols; + + /** + * The date symbols of the locale used for formatting values. + */ + private DateFormatSymbols dateSymbols; + /** + * A default format to use when a number pattern cannot be parsed. + */ + private Format defaultNumFormat; + /** + * A map to cache formats. Map formats + */ + private final Map formats = new HashMap(); + + /** + * stores the locale valid it the last formatting call + */ + private Locale locale; + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @return + */ + private Boolean use1904windowing; + /** + * Whether to use scientific Format. + * + * default is false + */ + private Boolean useScientificFormat; + + /** + * Creates a formatter using the given locale. + */ + public DataFormatter(Boolean use1904windowing, Locale locale, Boolean useScientificFormat) { + if (use1904windowing == null) { + this.use1904windowing = Boolean.FALSE; + } else { + this.use1904windowing = use1904windowing; + } + + if (locale == null) { + this.locale = Locale.getDefault(); + } else { + this.locale = locale; + } + + if (use1904windowing == null) { + this.useScientificFormat = Boolean.FALSE; + } else { + this.useScientificFormat = useScientificFormat; + } + + this.dateSymbols = DateFormatSymbols.getInstance(this.locale); + this.decimalSymbols = DecimalFormatSymbols.getInstance(this.locale); + } + + private Format getFormat(Double data, Short dataFormat, String dataFormatString) { + + // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. + // That however would require other code to be re factored. + // String[] formatBits = formatStrIn.split(";"); + // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; + // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; + String formatStr = dataFormatString; + + // Excel supports 2+ part conditional data formats, eg positive/negative/zero, + // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds + // of different formats for different ranges, just +ve/-ve, we need to + // handle these ourselves in a special way. + // For now, if we detect 2+ parts, we call out to CellFormat to handle it + // TODO Going forward, we should really merge the logic between the two classes + if (formatStr.contains(";") && + (formatStr.indexOf(';') != formatStr.lastIndexOf(';') + || rangeConditionalPattern.matcher(formatStr).matches() + )) { + try { + // Ask CellFormat to get a formatter for it + CellFormat cfmt = CellFormat.getInstance(locale, formatStr); + // CellFormat requires callers to identify date vs not, so do so + Object cellValueO = data; + if (DateUtils.isADateFormat(dataFormat, formatStr) && + // don't try to handle Date value 0, let a 3 or 4-part format take care of it + data.doubleValue() != 0.0) { + cellValueO = DateUtils.getJavaDate(data, use1904windowing); + } + // Wrap and return (non-cachable - CellFormat does that) + return new CellFormatResultWrapper(cfmt.apply(cellValueO)); + } catch (Exception e) { + LOGGER.warn("Formatting failed for format {}, falling back", formatStr, e); + } + } + + // See if we already have it cached + Format format = formats.get(formatStr); + if (format != null) { + return format; + } + + // Is it one of the special built in types, General or @? + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + format = getDefaultFormat(); + addFormat(formatStr, format); + return format; + } + + // Build a formatter, and cache it + format = createFormat(dataFormat, formatStr); + addFormat(formatStr, format); + return format; + } + + + + private Format createFormat(Short dataFormat, String dataFormatString) { + String formatStr = dataFormatString; + + Format format = checkSpecialConverter(formatStr); + if (format != null) { + return format; + } + + // Remove colour formatting if present + Matcher colourM = colorPattern.matcher(formatStr); + while (colourM.find()) { + String colour = colourM.group(); + + // Paranoid replacement... + int at = formatStr.indexOf(colour); + if (at == -1) {break;} + String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); + if (nFormatStr.equals(formatStr)) {break;} + + // Try again in case there's multiple + formatStr = nFormatStr; + colourM = colorPattern.matcher(formatStr); + } + + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); + while (m.find()) { + String match = m.group(); + String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); + if (symbol.indexOf('$') > -1) { + symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + symbol.substring(symbol.indexOf('$')); + } + formatStr = m.replaceAll(symbol); + m = localePatternGroup.matcher(formatStr); + } + + // Check for special cases + if (formatStr == null || formatStr.trim().length() == 0) { + return getDefaultFormat(); + } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return getDefaultFormat(); + } + + if (DateUtils.isADateFormat(dataFormat, formatStr)) { + return createDateFormat(formatStr); + } + // Excel supports fractions in format strings, which Java doesn't + if (formatStr.contains("#/") || formatStr.contains("?/")) { + String[] chunks = formatStr.split(";"); + for (String chunk1 : chunks) { + String chunk = chunk1.replaceAll("\\?", "#"); + Matcher matcher = fractionStripper.matcher(chunk); + chunk = matcher.replaceAll(" "); + chunk = chunk.replaceAll(" +", " "); + Matcher fractionMatcher = fractionPattern.matcher(chunk); + // take the first match + if (fractionMatcher.find()) { + String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat; + return new FractionFormat(wholePart, fractionMatcher.group(3)); + } + } + + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in + // fractions. + // String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", + // "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); + return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); + } + + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr); + } + return getDefaultFormat(); + } + + private Format checkSpecialConverter(String dataFormatString) { + if ("00000\\-0000".equals(dataFormatString) || "00000-0000".equals(dataFormatString)) { + return new ZipPlusFourFormat(); + } + if ("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString) + || "[<=9999999]###-####;(###) ###-####".equals(dataFormatString) + || "###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString) + || "###-####;(###) ###-####".equals(dataFormatString)) { + return new PhoneFormat(); + } + if ("000\\-00\\-0000".equals(dataFormatString) || "000-00-0000".equals(dataFormatString)) { + return new SSNFormat(); + } + return null; + } + + private Format createDateFormat(String pFormatStr) { + String formatStr = pFormatStr; + formatStr = formatStr.replaceAll("\\\\-", "-"); + formatStr = formatStr.replaceAll("\\\\,", ","); + formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char + formatStr = formatStr.replaceAll("\\\\ ", " "); + formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll(";@", ""); + formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy + formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting + formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates + formatStr = formatStr.replace("\"", ""); + + boolean hasAmPm = false; + Matcher amPmMatcher = amPmPattern.matcher(formatStr); + while (amPmMatcher.find()) { + formatStr = amPmMatcher.replaceAll("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + Matcher dateMatcher = daysAsText.matcher(formatStr); + if (dateMatcher.find()) { + String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E"); + formatStr = dateMatcher.replaceAll(match); + } + + // Convert excel date format to SimpleDateFormat. + // Excel uses lower and upper case 'm' for both minutes and months. + // From Excel help: + /* + The "m" or "mm" code must appear immediately after the "h" or"hh" + code or immediately before the "ss" code; otherwise, Microsoft + Excel displays the month instead of minutes." + */ + StringBuilder sb = new StringBuilder(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + boolean isElapsed = false; + for (int j = 0; j < chars.length; j++) { + char c = chars[j]; + if (c == '\'') { + sb.append(c); + j++; + + // skip until the next quote + while (j < chars.length) { + c = chars[j]; + sb.append(c); + if (c == '\'') { + break; + } + j++; + } + } else if (c == '[' && !isElapsed) { + isElapsed = true; + mIsMonth = false; + sb.append(c); + } else if (c == ']' && isElapsed) { + isElapsed = false; + sb.append(c); + } else if (isElapsed) { + if (c == 'h' || c == 'H') { + sb.append('H'); + } else if (c == 'm' || c == 'M') { + sb.append('m'); + } else if (c == 's' || c == 'S') { + sb.append('s'); + } else { + sb.append(c); + } + } else if (c == 'h' || c == 'H') { + mIsMonth = false; + if (hasAmPm) { + sb.append('h'); + } else { + sb.append('H'); + } + } else if (c == 'm' || c == 'M') { + if (mIsMonth) { + sb.append('M'); + ms.add(Integer.valueOf(sb.length() - 1)); + } else { + sb.append('m'); + } + } else if (c == 's' || c == 'S') { + sb.append('s'); + // if 'M' precedes 's' it should be minutes ('m') + for (int index : ms) { + if (sb.charAt(index) == 'M') { + sb.replace(index, index + 1, "m"); + } + } + mIsMonth = true; + ms.clear(); + } else if (Character.isLetter(c)) { + mIsMonth = true; + ms.clear(); + if (c == 'y' || c == 'Y') { + sb.append('y'); + } else if (c == 'd' || c == 'D') { + sb.append('d'); + } else { + sb.append(c); + } + } else { + if (Character.isWhitespace(c)) { + ms.clear(); + } + sb.append(c); + } + } + formatStr = sb.toString(); + + try { + return new ExcelStyleDateFormatter(formatStr, dateSymbols); + } catch (IllegalArgumentException iae) { + LOGGER.debug("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(); + } + + } + + private String cleanFormatForNumber(String formatStr) { + StringBuilder sb = new StringBuilder(formatStr); + // If they requested spacers, with "_", + // remove those as we don't do spacing + // If they requested full-column-width + // padding, with "*", remove those too + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + sb.deleteCharAt(i + 1); + } + // Remove the _ too + sb.deleteCharAt(i); + i--; + } + } + + // Now, handle the other aspects like + // quoting and scientific notation + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + // remove quotes and back slashes + if (c == '\\' || c == '"') { + sb.deleteCharAt(i); + i--; + + // for scientific/engineering notation + } else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') { + sb.deleteCharAt(i); + i--; + } + } + + return sb.toString(); + } + + private static class InternalDecimalFormatWithScale extends Format { + + private static final Pattern endsWithCommas = Pattern.compile("(,+)$"); + private BigDecimal divider; + private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private final DecimalFormat df; + + private static String trimTrailingCommas(String s) { + return s.replaceAll(",+$", ""); + } + + public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) { + df = new DecimalFormat(trimTrailingCommas(pattern), symbols); + setExcelStyleRoundingMode(df); + Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern); + if (endsWithCommasMatcher.find()) { + String commas = (endsWithCommasMatcher.group(1)); + BigDecimal temp = BigDecimal.ONE; + for (int i = 0; i < commas.length(); ++i) { + temp = temp.multiply(ONE_THOUSAND); + } + divider = temp; + } else { + divider = null; + } + } + + private Object scaleInput(Object obj) { + if (divider != null) { + if (obj instanceof BigDecimal) { + obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP); + } else if (obj instanceof Double) { + obj = (Double)obj / divider.doubleValue(); + } else { + throw new UnsupportedOperationException(); + } + } + return obj; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + obj = scaleInput(obj); + return df.format(obj, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + } + + private Format createNumberFormat(String formatStr) { + String format = cleanFormatForNumber(formatStr); + DecimalFormatSymbols symbols = decimalSymbols; + + // Do we need to change the grouping character? + // eg for a format like #'##0 which wants 12'345 not 12,345 + Matcher agm = alternateGrouping.matcher(format); + if (agm.find()) { + char grouping = agm.group(2).charAt(0); + // Only replace the grouping character if it is not the default + // grouping character for the US locale (',') in order to enable + // correct grouping for non-US locales. + if (grouping != ',') { + symbols = DecimalFormatSymbols.getInstance(locale); + + symbols.setGroupingSeparator(grouping); + String oldPart = agm.group(1); + String newPart = oldPart.replace(grouping, ','); + format = format.replace(oldPart, newPart); + } + } + + try { + return new InternalDecimalFormatWithScale(format, symbols); + } catch (IllegalArgumentException iae) { + LOGGER.error("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(); + } + } + + private Format getDefaultFormat() { + // for numeric cells try user supplied default + if (defaultNumFormat != null) { + return defaultNumFormat; + // otherwise use general format + } + defaultNumFormat = new ExcelGeneralNumberFormat(locale, useScientificFormat); + return defaultNumFormat; + } + + /** + * Performs Excel-style date formatting, using the supplied Date and format + */ + private String performDateFormatting(Date d, Format dateFormat) { + Format df = dateFormat != null ? dateFormat : getDefaultFormat(); + return df.format(d); + } + + /** + * Returns the formatted value of an Excel date as a String based on the cell's DataFormat. + * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc. + *

+ * If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a + * number format, or no rules apply, the cell's style format is used. If the style does not have a format, the + * default date format is applied. + * + * @param data to format + * @param dataFormat + * @param dataFormatString + * @return Formatted value + */ + private String getFormattedDateString(Double data, Short dataFormat, String dataFormatString) { + Format dateFormat = getFormat(data, dataFormat, dataFormatString); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(data); + } + return performDateFormatting(DateUtils.getJavaDate(data, use1904windowing), dateFormat); + } + + /** + * Returns the formatted value of an Excel number as a String based on the cell's DataFormat. + * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800) + * 555-1234". + *

+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell + * style. + * + * @param data to format + * @param dataFormat + * @param dataFormatString + * @return a formatted number string + */ + private String getFormattedNumberString(BigDecimal data, Short dataFormat, String dataFormatString) { + Format numberFormat = getFormat(data.doubleValue(), dataFormat, dataFormatString); + return E_NOTATION_PATTERN.matcher(numberFormat.format(data)).replaceFirst("E+$1"); + } + + /** + * Format data. + * + * @param data + * @param dataFormat + * @param dataFormatString + * @return + */ + public String format(BigDecimal data, Short dataFormat, String dataFormatString) { + if (DateUtils.isADateFormat(dataFormat, dataFormatString)) { + return getFormattedDateString(data.doubleValue(), dataFormat, dataFormatString); + } + return getFormattedNumberString(data, dataFormat, dataFormatString); + } + + /** + *

+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is + * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells + * with the General format. + *

+ *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param format A Format instance to be used as a default + * @see Format#format + */ + public void setDefaultNumberFormat(Format format) { + for (Map.Entry entry : formats.entrySet()) { + if (entry.getValue() == defaultNumFormat) { + entry.setValue(format); + } + } + defaultNumFormat = format; + } + + /** + * Adds a new format to the available formats. + *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param excelFormatStr The data format string + * @param format A Format instance + */ + public void addFormat(String excelFormatStr, Format format) { + formats.put(excelFormatStr, format); + } + + // Some custom formats + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + private static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT); + DecimalFormat result = new DecimalFormat(fmt, dsf); + result.setParseIntegerOnly(true); + return result; + } + + /** + * Enables excel style rounding mode (round half up) on the Decimal Format given. + */ + public static void setExcelStyleRoundingMode(DecimalFormat format) { + setExcelStyleRoundingMode(format, RoundingMode.HALF_UP); + } + + /** + * Enables custom rounding mode on the given Decimal Format. + * + * @param format DecimalFormat + * @param roundingMode RoundingMode + */ + public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { + format.setRoundingMode(roundingMode); + } + + /** + * Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class SSNFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private SSNFormat() { + // enforce singleton + } + + /** + * Format a number as an SSN + */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class ZipPlusFourFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private ZipPlusFourFormat() { + // enforce singleton + } + + /** + * Format a number as Zip + 4 + */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class PhoneFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + + private PhoneFormat() { + // enforce singleton + } + + /** + * Format a number as a phone number + */ + public static String format(Number num) { + String result = df.format(num); + StringBuilder sb = new StringBuilder(); + String seg1, seg2, seg3; + int len = result.length(); + if (len <= 4) { + return result; + } + + seg3 = result.substring(len - 4, len); + seg2 = result.substring(Math.max(0, len - 7), len - 4); + seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7)); + + if (seg1.trim().length() > 0) { + sb.append('(').append(seg1).append(") "); + } + if (seg2.trim().length() > 0) { + sb.append(seg2).append('-'); + } + sb.append(seg3); + return sb.toString(); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Workaround until we merge {@link org.apache.poi.ss.usermodel.DataFormatter} with {@link CellFormat}. Constant, + * non-cachable wrapper around a + * {@link CellFormatResult} + */ + private final class CellFormatResultWrapper extends Format { + private final CellFormatResult result; + + private CellFormatResultWrapper(CellFormatResult result) { + this.result = result; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(result.text.trim()); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return null; // Not supported + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/ExcelGeneralNumberFormat.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/ExcelGeneralNumberFormat.java new file mode 100644 index 0000000..ab695e8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/format/ExcelGeneralNumberFormat.java @@ -0,0 +1,81 @@ +package ai.chat2db.excel.metadata.format; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.util.Locale; + +import org.apache.poi.ss.usermodel.DataFormatter; + +/** + * Written with reference to {@link org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat }. + *

+ * Supported Do not use scientific notation. + * + * @author JiaJu Zhuang + **/ +public class ExcelGeneralNumberFormat extends Format { + + private static final long serialVersionUID = 1L; + + private static final MathContext TO_10_SF = new MathContext(10, RoundingMode.HALF_UP); + + private final DecimalFormatSymbols decimalSymbols; + private final DecimalFormat integerFormat; + private final DecimalFormat decimalFormat; + private final DecimalFormat scientificFormat; + + public ExcelGeneralNumberFormat(final Locale locale, final boolean useScientificFormat) { + decimalSymbols = DecimalFormatSymbols.getInstance(locale); + // Supported Do not use scientific notation. + if (useScientificFormat) { + scientificFormat = new DecimalFormat("0.#####E0", decimalSymbols); + } else { + scientificFormat = new DecimalFormat("#", decimalSymbols); + } + org.apache.poi.ss.usermodel.DataFormatter.setExcelStyleRoundingMode(scientificFormat); + integerFormat = new DecimalFormat("#", decimalSymbols); + org.apache.poi.ss.usermodel.DataFormatter.setExcelStyleRoundingMode(integerFormat); + decimalFormat = new DecimalFormat("#.##########", decimalSymbols); + DataFormatter.setExcelStyleRoundingMode(decimalFormat); + } + + @Override + public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { + final double value; + if (number instanceof Number) { + value = ((Number) number).doubleValue(); + if (Double.isInfinite(value) || Double.isNaN(value)) { + return integerFormat.format(number, toAppendTo, pos); + } + } else { + // testBug54786 gets here with a date, so retain previous behaviour + return integerFormat.format(number, toAppendTo, pos); + } + + final double abs = Math.abs(value); + if (abs >= 1E11 || (abs <= 1E-10 && abs > 0)) { + return scientificFormat.format(number, toAppendTo, pos); + } else if (Math.floor(value) == value || abs >= 1E10) { + // integer, or integer portion uses all 11 allowed digits + return integerFormat.format(number, toAppendTo, pos); + } + // Non-integers of non-scientific magnitude are formatted as "up to 11 + // numeric characters, with the decimal point counting as a numeric + // character". We know there is a decimal point, so limit to 10 digits. + // https://support.microsoft.com/en-us/kb/65903 + final double rounded = new BigDecimal(value).round(TO_10_SF).doubleValue(); + return decimalFormat.format(rounded, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ColumnWidthProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ColumnWidthProperty.java new file mode 100644 index 0000000..710b52c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ColumnWidthProperty.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.ColumnWidth; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +public class ColumnWidthProperty { + private Integer width; + + public ColumnWidthProperty(Integer width) { + this.width = width; + } + + public static ColumnWidthProperty build(ColumnWidth columnWidth) { + if (columnWidth == null || columnWidth.value() < 0) { + return null; + } + return new ColumnWidthProperty(columnWidth.value()); + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/DateTimeFormatProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/DateTimeFormatProperty.java new file mode 100644 index 0000000..ef244d0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/DateTimeFormatProperty.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.util.BooleanUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class DateTimeFormatProperty { + private String format; + private Boolean use1904windowing; + + public DateTimeFormatProperty(String format, Boolean use1904windowing) { + this.format = format; + this.use1904windowing = use1904windowing; + } + + public static DateTimeFormatProperty build(DateTimeFormat dateTimeFormat) { + if (dateTimeFormat == null) { + return null; + } + return new DateTimeFormatProperty(dateTimeFormat.value(), + BooleanUtils.isTrue(dateTimeFormat.use1904windowing().getBooleanValue())); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelContentProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelContentProperty.java new file mode 100644 index 0000000..5ade043 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelContentProperty.java @@ -0,0 +1,44 @@ +package ai.chat2db.excel.metadata.property; + +import java.lang.reflect.Field; + +import ai.chat2db.excel.converters.Converter; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcelContentProperty { + public static final ExcelContentProperty EMPTY = new ExcelContentProperty(); + + /** + * Java field + */ + private Field field; + /** + * Custom defined converters + */ + private Converter converter; + /** + * date time format + */ + private DateTimeFormatProperty dateTimeFormatProperty; + /** + * number format + */ + private NumberFormatProperty numberFormatProperty; + /** + * Content style + */ + private StyleProperty contentStyleProperty; + /** + * Content font + */ + private FontProperty contentFontProperty; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelHeadProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelHeadProperty.java new file mode 100644 index 0000000..63f3535 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/ExcelHeadProperty.java @@ -0,0 +1,141 @@ +package ai.chat2db.excel.metadata.property; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.metadata.FieldCache; +import ai.chat2db.excel.metadata.FieldWrapper; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.write.metadata.holder.AbstractWriteHolder; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Define the header attribute of excel + * + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcelHeadProperty { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelHeadProperty.class); + /** + * Custom class + */ + private Class headClazz; + /** + * The types of head + */ + private HeadKindEnum headKind; + /** + * The number of rows in the line with the most rows + */ + private int headRowNumber; + /** + * Configuration header information + */ + private Map headMap; + + public ExcelHeadProperty(ConfigurationHolder configurationHolder, Class headClazz, List> head) { + this.headClazz = headClazz; + headMap = new TreeMap<>(); + headKind = HeadKindEnum.NONE; + headRowNumber = 0; + if (head != null && !head.isEmpty()) { + int headIndex = 0; + for (int i = 0; i < head.size(); i++) { + if (configurationHolder instanceof AbstractWriteHolder) { + if (((AbstractWriteHolder)configurationHolder).ignore(null, i)) { + continue; + } + } + headMap.put(headIndex, new Head(headIndex, null, null, head.get(i), Boolean.FALSE, Boolean.TRUE)); + headIndex++; + } + headKind = HeadKindEnum.STRING; + } + // convert headClazz to head + initColumnProperties(configurationHolder); + + initHeadRowNumber(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("The initialization sheet/table 'ExcelHeadProperty' is complete , head kind is {}", headKind); + } + } + + private void initHeadRowNumber() { + headRowNumber = 0; + for (Head head : headMap.values()) { + List list = head.getHeadNameList(); + if (list != null && list.size() > headRowNumber) { + headRowNumber = list.size(); + } + } + for (Head head : headMap.values()) { + List list = head.getHeadNameList(); + if (list != null && !list.isEmpty() && list.size() < headRowNumber) { + int lack = headRowNumber - list.size(); + int last = list.size() - 1; + for (int i = 0; i < lack; i++) { + list.add(list.get(last)); + } + } + } + } + + private void initColumnProperties(ConfigurationHolder configurationHolder) { + if (headClazz == null) { + return; + } + FieldCache fieldCache = ClassUtils.declaredFields(headClazz, configurationHolder); + + for (Map.Entry entry : fieldCache.getSortedFieldMap().entrySet()) { + initOneColumnProperty(entry.getKey(), entry.getValue(), + fieldCache.getIndexFieldMap().containsKey(entry.getKey())); + } + headKind = HeadKindEnum.CLASS; + } + + /** + * Initialization column property + * + * @param index + * @param field + * @param forceIndex + * @return Ignore current field + */ + private void initOneColumnProperty(int index, FieldWrapper field, Boolean forceIndex) { + List tmpHeadList = new ArrayList<>(); + boolean notForceName = field.getHeads() == null || field.getHeads().length == 0 + || (field.getHeads().length == 1 && StringUtils.isEmpty(field.getHeads()[0])); + if (headMap.containsKey(index)) { + tmpHeadList.addAll(headMap.get(index).getHeadNameList()); + } else { + if (notForceName) { + tmpHeadList.add(field.getFieldName()); + } else { + Collections.addAll(tmpHeadList, field.getHeads()); + } + } + Head head = new Head(index, field.getField(), field.getFieldName(), tmpHeadList, forceIndex, !notForceName); + headMap.put(index, head); + } + + public boolean hasHead() { + return headKind != HeadKindEnum.NONE; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/FontProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/FontProperty.java new file mode 100644 index 0000000..da343d0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/FontProperty.java @@ -0,0 +1,140 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.ContentFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hssf.usermodel.HSSFPalette; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FontProperty { + /** + * The name for the font (i.e. Arial) + */ + private String fontName; + /** + * Height in the familiar unit of measure - points + */ + private Short fontHeightInPoints; + /** + * Whether to use italics or not + */ + private Boolean italic; + /** + * Whether to use a strikeout horizontal line through the text or not + */ + private Boolean strikeout; + /** + * The color for the font + * + * @see Font#COLOR_NORMAL + * @see Font#COLOR_RED + * @see HSSFPalette#getColor(short) + * @see IndexedColors + */ + private Short color; + /** + * Set normal, super or subscript. + * + * @see Font#SS_NONE + * @see Font#SS_SUPER + * @see Font#SS_SUB + */ + private Short typeOffset; + /** + * set type of text underlining to use + * + * @see Font#U_NONE + * @see Font#U_SINGLE + * @see Font#U_DOUBLE + * @see Font#U_SINGLE_ACCOUNTING + * @see Font#U_DOUBLE_ACCOUNTING + */ + + private Byte underline; + /** + * Set character-set to use. + * + * @see FontCharset + * @see Font#ANSI_CHARSET + * @see Font#DEFAULT_CHARSET + * @see Font#SYMBOL_CHARSET + */ + private Integer charset; + /** + * Bold + */ + private Boolean bold; + + public static FontProperty build(HeadFontStyle headFontStyle) { + if (headFontStyle == null) { + return null; + } + FontProperty styleProperty = new FontProperty(); + if (StringUtils.isNotBlank(headFontStyle.fontName())) { + styleProperty.setFontName(headFontStyle.fontName()); + } + if (headFontStyle.fontHeightInPoints() >= 0) { + styleProperty.setFontHeightInPoints(headFontStyle.fontHeightInPoints()); + } + styleProperty.setItalic(headFontStyle.italic().getBooleanValue()); + styleProperty.setStrikeout(headFontStyle.strikeout().getBooleanValue()); + if (headFontStyle.color() >= 0) { + styleProperty.setColor(headFontStyle.color()); + } + if (headFontStyle.typeOffset() >= 0) { + styleProperty.setTypeOffset(headFontStyle.typeOffset()); + } + if (headFontStyle.underline() >= 0) { + styleProperty.setUnderline(headFontStyle.underline()); + } + if (headFontStyle.charset() >= 0) { + styleProperty.setCharset(headFontStyle.charset()); + } + styleProperty.setBold(headFontStyle.bold().getBooleanValue()); + return styleProperty; + } + + public static FontProperty build(ContentFontStyle contentFontStyle) { + if (contentFontStyle == null) { + return null; + } + FontProperty styleProperty = new FontProperty(); + if (StringUtils.isNotBlank(contentFontStyle.fontName())) { + styleProperty.setFontName(contentFontStyle.fontName()); + } + if (contentFontStyle.fontHeightInPoints() >= 0) { + styleProperty.setFontHeightInPoints(contentFontStyle.fontHeightInPoints()); + } + styleProperty.setItalic(contentFontStyle.italic().getBooleanValue()); + styleProperty.setStrikeout(contentFontStyle.strikeout().getBooleanValue()); + if (contentFontStyle.color() >= 0) { + styleProperty.setColor(contentFontStyle.color()); + } + if (contentFontStyle.typeOffset() >= 0) { + styleProperty.setTypeOffset(contentFontStyle.typeOffset()); + } + if (contentFontStyle.underline() >= 0) { + styleProperty.setUnderline(contentFontStyle.underline()); + } + if (contentFontStyle.charset() >= 0) { + styleProperty.setCharset(contentFontStyle.charset()); + } + styleProperty.setBold(contentFontStyle.bold().getBooleanValue()); + return styleProperty; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/LoopMergeProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/LoopMergeProperty.java new file mode 100644 index 0000000..c33ed39 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/LoopMergeProperty.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.ContentLoopMerge; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +public class LoopMergeProperty { + /** + * Each row + */ + private int eachRow; + /** + * Extend column + */ + private int columnExtend; + + public LoopMergeProperty(int eachRow, int columnExtend) { + this.eachRow = eachRow; + this.columnExtend = columnExtend; + } + + public static LoopMergeProperty build(ContentLoopMerge contentLoopMerge) { + if (contentLoopMerge == null) { + return null; + } + return new LoopMergeProperty(contentLoopMerge.eachRow(), contentLoopMerge.columnExtend()); + } + + public int getEachRow() { + return eachRow; + } + + public void setEachRow(int eachRow) { + this.eachRow = eachRow; + } + + public int getColumnExtend() { + return columnExtend; + } + + public void setColumnExtend(int columnExtend) { + this.columnExtend = columnExtend; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/NumberFormatProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/NumberFormatProperty.java new file mode 100644 index 0000000..b17573a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/NumberFormatProperty.java @@ -0,0 +1,43 @@ +package ai.chat2db.excel.metadata.property; + +import java.math.RoundingMode; + +import ai.chat2db.excel.annotation.format.NumberFormat; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +public class NumberFormatProperty { + private String format; + private RoundingMode roundingMode; + + public NumberFormatProperty(String format, RoundingMode roundingMode) { + this.format = format; + this.roundingMode = roundingMode; + } + + public static NumberFormatProperty build(NumberFormat numberFormat) { + if (numberFormat == null) { + return null; + } + return new NumberFormatProperty(numberFormat.value(), numberFormat.roundingMode()); + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public RoundingMode getRoundingMode() { + return roundingMode; + } + + public void setRoundingMode(RoundingMode roundingMode) { + this.roundingMode = roundingMode; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/OnceAbsoluteMergeProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/OnceAbsoluteMergeProperty.java new file mode 100644 index 0000000..4b5e6f1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/OnceAbsoluteMergeProperty.java @@ -0,0 +1,74 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.OnceAbsoluteMerge; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +public class OnceAbsoluteMergeProperty { + /** + * First row + */ + private int firstRowIndex; + /** + * Last row + */ + private int lastRowIndex; + /** + * First column + */ + private int firstColumnIndex; + /** + * Last row + */ + private int lastColumnIndex; + + public OnceAbsoluteMergeProperty(int firstRowIndex, int lastRowIndex, int firstColumnIndex, int lastColumnIndex) { + this.firstRowIndex = firstRowIndex; + this.lastRowIndex = lastRowIndex; + this.firstColumnIndex = firstColumnIndex; + this.lastColumnIndex = lastColumnIndex; + } + + public static OnceAbsoluteMergeProperty build(OnceAbsoluteMerge onceAbsoluteMerge) { + if (onceAbsoluteMerge == null) { + return null; + } + return new OnceAbsoluteMergeProperty(onceAbsoluteMerge.firstRowIndex(), onceAbsoluteMerge.lastRowIndex(), + onceAbsoluteMerge.firstColumnIndex(), onceAbsoluteMerge.lastColumnIndex()); + } + + public int getFirstRowIndex() { + return firstRowIndex; + } + + public void setFirstRowIndex(int firstRowIndex) { + this.firstRowIndex = firstRowIndex; + } + + public int getLastRowIndex() { + return lastRowIndex; + } + + public void setLastRowIndex(int lastRowIndex) { + this.lastRowIndex = lastRowIndex; + } + + public int getFirstColumnIndex() { + return firstColumnIndex; + } + + public void setFirstColumnIndex(int firstColumnIndex) { + this.firstColumnIndex = firstColumnIndex; + } + + public int getLastColumnIndex() { + return lastColumnIndex; + } + + public void setLastColumnIndex(int lastColumnIndex) { + this.lastColumnIndex = lastColumnIndex; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/RowHeightProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/RowHeightProperty.java new file mode 100644 index 0000000..78f1ee5 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/RowHeightProperty.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadRowHeight; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +public class RowHeightProperty { + private Short height; + + public RowHeightProperty(Short height) { + this.height = height; + } + + public static RowHeightProperty build(HeadRowHeight headRowHeight) { + if (headRowHeight == null || headRowHeight.value() < 0) { + return null; + } + return new RowHeightProperty(headRowHeight.value()); + } + + public static RowHeightProperty build(ContentRowHeight contentRowHeight) { + if (contentRowHeight == null || contentRowHeight.value() < 0) { + return null; + } + return new RowHeightProperty(contentRowHeight.value()); + } + + public Short getHeight() { + return height; + } + + public void setHeight(Short height) { + this.height = height; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/StyleProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/StyleProperty.java new file mode 100644 index 0000000..0c408a8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/metadata/property/StyleProperty.java @@ -0,0 +1,242 @@ +package ai.chat2db.excel.metadata.property; + +import ai.chat2db.excel.annotation.write.style.ContentStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.BuiltinFormats; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IgnoredErrorType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * Configuration from annotations + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class StyleProperty { + /** + * Set the data format (must be a valid format). Built in formats are defined at {@link BuiltinFormats}. + */ + private DataFormatData dataFormatData; + /** + * Set the font for this style + */ + private WriteFont writeFont; + /** + * Set the cell's using this style to be hidden + */ + private Boolean hidden; + + /** + * Set the cell's using this style to be locked + */ + private Boolean locked; + /** + * Turn on or off "Quote Prefix" or "123 Prefix" for the style, which is used to tell Excel that the thing which + * looks like a number or a formula shouldn't be treated as on. Turning this on is somewhat (but not completely, see + * {@link IgnoredErrorType}) like prefixing the cell value with a ' in Excel + */ + private Boolean quotePrefix; + /** + * Set the type of horizontal alignment for the cell + */ + private HorizontalAlignment horizontalAlignment; + /** + * Set whether the text should be wrapped. Setting this flag to true make all content visible within a + * cell by displaying it on multiple lines + */ + private Boolean wrapped; + /** + * Set the type of vertical alignment for the cell + */ + private VerticalAlignment verticalAlignment; + /** + * Set the degree of rotation for the text in the cell. + * + * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF uses values from 0 to 180 degrees. The + * implementations of this method will map between these two value-ranges accordingly, however the corresponding + * getter is returning values in the range mandated by the current type of Excel file-format that this CellStyle is + * applied to. + */ + private Short rotation; + /** + * Set the number of spaces to indent the text in the cell + */ + private Short indent; + /** + * Set the type of border to use for the left border of the cell + */ + private BorderStyle borderLeft; + /** + * Set the type of border to use for the right border of the cell + */ + private BorderStyle borderRight; + /** + * Set the type of border to use for the top border of the cell + */ + private BorderStyle borderTop; + + /** + * Set the type of border to use for the bottom border of the cell + */ + private BorderStyle borderBottom; + /** + * Set the color to use for the left border + * + * @see IndexedColors + */ + private Short leftBorderColor; + + /** + * Set the color to use for the right border + * + * @see IndexedColors + */ + private Short rightBorderColor; + + /** + * Set the color to use for the top border + * + * @see IndexedColors + */ + private Short topBorderColor; + /** + * Set the color to use for the bottom border + * + * @see IndexedColors + */ + private Short bottomBorderColor; + /** + * Setting to one fills the cell with the foreground color... No idea about other values + * + * @see FillPatternType#SOLID_FOREGROUND + */ + private FillPatternType fillPatternType; + + /** + * Set the background fill color. + * + * @see IndexedColors + */ + private Short fillBackgroundColor; + + /** + * Set the foreground fill color Note: Ensure Foreground color is set prior to background color. + * + * @see IndexedColors + */ + private Short fillForegroundColor; + /** + * Controls if the Cell should be auto-sized to shrink to fit if the text is too long + */ + private Boolean shrinkToFit; + + public static StyleProperty build(HeadStyle headStyle) { + if (headStyle == null) { + return null; + } + StyleProperty styleProperty = new StyleProperty(); + if (headStyle.dataFormat() >= 0) { + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex(headStyle.dataFormat()); + styleProperty.setDataFormatData(dataFormatData); + } + styleProperty.setHidden(headStyle.hidden().getBooleanValue()); + styleProperty.setLocked(headStyle.locked().getBooleanValue()); + styleProperty.setQuotePrefix(headStyle.quotePrefix().getBooleanValue()); + styleProperty.setHorizontalAlignment(headStyle.horizontalAlignment().getPoiHorizontalAlignment()); + styleProperty.setWrapped(headStyle.wrapped().getBooleanValue()); + styleProperty.setVerticalAlignment(headStyle.verticalAlignment().getPoiVerticalAlignmentEnum()); + if (headStyle.rotation() >= 0) { + styleProperty.setRotation(headStyle.rotation()); + } + if (headStyle.indent() >= 0) { + styleProperty.setIndent(headStyle.indent()); + } + styleProperty.setBorderLeft(headStyle.borderLeft().getPoiBorderStyle()); + styleProperty.setBorderRight(headStyle.borderRight().getPoiBorderStyle()); + styleProperty.setBorderTop(headStyle.borderTop().getPoiBorderStyle()); + styleProperty.setBorderBottom(headStyle.borderBottom().getPoiBorderStyle()); + if (headStyle.leftBorderColor() >= 0) { + styleProperty.setLeftBorderColor(headStyle.leftBorderColor()); + } + if (headStyle.rightBorderColor() >= 0) { + styleProperty.setRightBorderColor(headStyle.rightBorderColor()); + } + if (headStyle.topBorderColor() >= 0) { + styleProperty.setTopBorderColor(headStyle.topBorderColor()); + } + if (headStyle.bottomBorderColor() >= 0) { + styleProperty.setBottomBorderColor(headStyle.bottomBorderColor()); + } + styleProperty.setFillPatternType(headStyle.fillPatternType().getPoiFillPatternType()); + if (headStyle.fillBackgroundColor() >= 0) { + styleProperty.setFillBackgroundColor(headStyle.fillBackgroundColor()); + } + if (headStyle.fillForegroundColor() >= 0) { + styleProperty.setFillForegroundColor(headStyle.fillForegroundColor()); + } + styleProperty.setShrinkToFit(headStyle.shrinkToFit().getBooleanValue()); + return styleProperty; + } + + public static StyleProperty build(ContentStyle contentStyle) { + if (contentStyle == null) { + return null; + } + StyleProperty styleProperty = new StyleProperty(); + if (contentStyle.dataFormat() >= 0) { + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex(contentStyle.dataFormat()); + styleProperty.setDataFormatData(dataFormatData); + } + styleProperty.setHidden(contentStyle.hidden().getBooleanValue()); + styleProperty.setLocked(contentStyle.locked().getBooleanValue()); + styleProperty.setQuotePrefix(contentStyle.quotePrefix().getBooleanValue()); + styleProperty.setHorizontalAlignment(contentStyle.horizontalAlignment().getPoiHorizontalAlignment()); + styleProperty.setWrapped(contentStyle.wrapped().getBooleanValue()); + styleProperty.setVerticalAlignment(contentStyle.verticalAlignment().getPoiVerticalAlignmentEnum()); + if (contentStyle.rotation() >= 0) { + styleProperty.setRotation(contentStyle.rotation()); + } + if (contentStyle.indent() >= 0) { + styleProperty.setIndent(contentStyle.indent()); + } + styleProperty.setBorderLeft(contentStyle.borderLeft().getPoiBorderStyle()); + styleProperty.setBorderRight(contentStyle.borderRight().getPoiBorderStyle()); + styleProperty.setBorderTop(contentStyle.borderTop().getPoiBorderStyle()); + styleProperty.setBorderBottom(contentStyle.borderBottom().getPoiBorderStyle()); + if (contentStyle.leftBorderColor() >= 0) { + styleProperty.setLeftBorderColor(contentStyle.leftBorderColor()); + } + if (contentStyle.rightBorderColor() >= 0) { + styleProperty.setRightBorderColor(contentStyle.rightBorderColor()); + } + if (contentStyle.topBorderColor() >= 0) { + styleProperty.setTopBorderColor(contentStyle.topBorderColor()); + } + if (contentStyle.bottomBorderColor() >= 0) { + styleProperty.setBottomBorderColor(contentStyle.bottomBorderColor()); + } + styleProperty.setFillPatternType(contentStyle.fillPatternType().getPoiFillPatternType()); + if (contentStyle.fillBackgroundColor() >= 0) { + styleProperty.setFillBackgroundColor(contentStyle.fillBackgroundColor()); + } + if (contentStyle.fillForegroundColor() >= 0) { + styleProperty.setFillForegroundColor(contentStyle.fillForegroundColor()); + } + styleProperty.setShrinkToFit(contentStyle.shrinkToFit().getBooleanValue()); + return styleProperty; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/AbstractExcelReaderParameterBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/AbstractExcelReaderParameterBuilder.java new file mode 100644 index 0000000..7710a06 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/AbstractExcelReaderParameterBuilder.java @@ -0,0 +1,59 @@ +package ai.chat2db.excel.read.builder; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.read.metadata.ReadBasicParameter; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.metadata.AbstractParameterBuilder; + +/** + * Build ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractExcelReaderParameterBuilder extends AbstractParameterBuilder { + /** + * Count the number of added heads when read sheet. + * + *

+ * 0 - This Sheet has no head ,since the first row are the data + *

+ * 1 - This Sheet has one row head , this is the default + *

+ * 2 - This Sheet has two row head ,since the third row is the data + * + * @param headRowNumber + * @return + */ + public T headRowNumber(Integer headRowNumber) { + parameter().setHeadRowNumber(headRowNumber); + return self(); + } + + /** + * Whether to use scientific Format. + * + * default is false + * + * @param useScientificFormat + * @return + */ + public T useScientificFormat(Boolean useScientificFormat) { + parameter().setUseScientificFormat(useScientificFormat); + return self(); + } + + /** + * Custom type listener run after default + * + * @param readListener + * @return + */ + public T registerReadListener(ReadListener readListener) { + if (parameter().getCustomReadListenerList() == null) { + parameter().setCustomReadListenerList(ListUtils.newArrayList()); + } + parameter().getCustomReadListenerList().add(readListener); + return self(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderBuilder.java new file mode 100644 index 0000000..709e79e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderBuilder.java @@ -0,0 +1,266 @@ +package ai.chat2db.excel.read.builder; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.List; + +import javax.xml.parsers.SAXParserFactory; + +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.cache.selector.ReadCacheSelector; +import ai.chat2db.excel.cache.selector.SimpleReadCacheSelector; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.event.SyncReadListener; +import ai.chat2db.excel.read.listener.ModelBuildEventListener; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * Build ExcelWriter + * + * @author Jiaju Zhuang + */ +public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder { + /** + * Workbook + */ + private final ReadWorkbook readWorkbook; + + public ExcelReaderBuilder() { + this.readWorkbook = new ReadWorkbook(); + } + + public ExcelReaderBuilder excelType(ExcelTypeEnum excelType) { + readWorkbook.setExcelType(excelType); + return this; + } + + /** + * Read InputStream + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + public ExcelReaderBuilder file(InputStream inputStream) { + readWorkbook.setInputStream(inputStream); + return this; + } + + /** + * Read file + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + public ExcelReaderBuilder file(File file) { + readWorkbook.setFile(file); + return this; + } + + /** + * Read file + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + public ExcelReaderBuilder file(String pathName) { + return file(new File(pathName)); + } + + /** + * charset. + * Only work on the CSV file + */ + public ExcelReaderBuilder charset(Charset charset) { + readWorkbook.setCharset(charset); + return this; + } + + /** + * Mandatory use 'inputStream' .Default is false. + *

+ * if false, Will transfer 'inputStream' to temporary files to improve efficiency + */ + public ExcelReaderBuilder mandatoryUseInputStream(Boolean mandatoryUseInputStream) { + readWorkbook.setMandatoryUseInputStream(mandatoryUseInputStream); + return this; + } + + /** + * Default true + * + * @param autoCloseStream + * @return + */ + public ExcelReaderBuilder autoCloseStream(Boolean autoCloseStream) { + readWorkbook.setAutoCloseStream(autoCloseStream); + return this; + } + + /** + * Ignore empty rows.Default is true. + * + * @param ignoreEmptyRow + * @return + */ + public ExcelReaderBuilder ignoreEmptyRow(Boolean ignoreEmptyRow) { + readWorkbook.setIgnoreEmptyRow(ignoreEmptyRow); + return this; + } + + /** + * This object can be read in the Listener {@link AnalysisEventListener#invoke(Object, AnalysisContext)} + * {@link AnalysisContext#getCustom()} + * + * @param customObject + * @return + */ + public ExcelReaderBuilder customObject(Object customObject) { + readWorkbook.setCustomObject(customObject); + return this; + } + + /** + * A cache that stores temp data to save memory. + * + * @param readCache + * @return + */ + public ExcelReaderBuilder readCache(ReadCache readCache) { + readWorkbook.setReadCache(readCache); + return this; + } + + /** + * Select the cache.Default use {@link SimpleReadCacheSelector} + * + * @param readCacheSelector + * @return + */ + public ExcelReaderBuilder readCacheSelector(ReadCacheSelector readCacheSelector) { + readWorkbook.setReadCacheSelector(readCacheSelector); + return this; + } + + /** + * Whether the encryption + * + * @param password + * @return + */ + public ExcelReaderBuilder password(String password) { + readWorkbook.setPassword(password); + return this; + } + + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @param xlsxSAXParserFactoryName + * @return + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + */ + public ExcelReaderBuilder xlsxSAXParserFactoryName(String xlsxSAXParserFactoryName) { + readWorkbook.setXlsxSAXParserFactoryName(xlsxSAXParserFactoryName); + return this; + } + + /** + * Read some extra information, not by default + * + * @param extraType extra information type + * @return + */ + public ExcelReaderBuilder extraRead(CellExtraTypeEnum extraType) { + if (readWorkbook.getExtraReadSet() == null) { + readWorkbook.setExtraReadSet(new HashSet()); + } + readWorkbook.getExtraReadSet().add(extraType); + return this; + } + + /** + * Whether to use the default listener, which is used by default. + *

+ * The {@link ModelBuildEventListener} is loaded by default to convert the object. + * + * @param useDefaultListener + * @return + */ + public ExcelReaderBuilder useDefaultListener(Boolean useDefaultListener) { + readWorkbook.setUseDefaultListener(useDefaultListener); + return this; + } + + /** + * Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type. + * Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`. + * + * @see ReadDefaultReturnEnum + */ + public ExcelReaderBuilder readDefaultReturn(ReadDefaultReturnEnum readDefaultReturn) { + readWorkbook.setReadDefaultReturn(readDefaultReturn); + return this; + } + + public ExcelReader build() { + return new ExcelReader(readWorkbook); + } + + public void doReadAll() { + try (ExcelReader excelReader = build()) { + excelReader.readAll(); + } + } + + /** + * Synchronous reads return results + * + * @return + */ + public List doReadAllSync() { + SyncReadListener syncReadListener = new SyncReadListener(); + registerReadListener(syncReadListener); + try (ExcelReader excelReader = build()) { + excelReader.readAll(); + excelReader.finish(); + } + return (List)syncReadListener.getList(); + } + + public ExcelReaderSheetBuilder sheet() { + return sheet(null, null); + } + + public ExcelReaderSheetBuilder sheet(Integer sheetNo) { + return sheet(sheetNo, null); + } + + public ExcelReaderSheetBuilder sheet(String sheetName) { + return sheet(null, sheetName); + } + + public ExcelReaderSheetBuilder sheet(Integer sheetNo, String sheetName) { + ExcelReaderSheetBuilder excelReaderSheetBuilder = new ExcelReaderSheetBuilder(build()); + if (sheetNo != null) { + excelReaderSheetBuilder.sheetNo(sheetNo); + } + if (sheetName != null) { + excelReaderSheetBuilder.sheetName(sheetName); + } + return excelReaderSheetBuilder; + } + + @Override + protected ReadWorkbook parameter() { + return readWorkbook; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderSheetBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderSheetBuilder.java new file mode 100644 index 0000000..0df651a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/builder/ExcelReaderSheetBuilder.java @@ -0,0 +1,89 @@ +package ai.chat2db.excel.read.builder; + +import java.util.List; + +import ai.chat2db.excel.event.SyncReadListener; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelGenerateException; + +/** + * Build sheet + * + * @author Jiaju Zhuang + */ +public class ExcelReaderSheetBuilder extends AbstractExcelReaderParameterBuilder { + private ExcelReader excelReader; + /** + * Sheet + */ + private final ReadSheet readSheet; + + public ExcelReaderSheetBuilder() { + this.readSheet = new ReadSheet(); + } + + public ExcelReaderSheetBuilder(ExcelReader excelReader) { + this.readSheet = new ReadSheet(); + this.excelReader = excelReader; + } + + /** + * Starting from 0 + * + * @param sheetNo + * @return + */ + public ExcelReaderSheetBuilder sheetNo(Integer sheetNo) { + readSheet.setSheetNo(sheetNo); + return this; + } + + /** + * sheet name + * + * @param sheetName + * @return + */ + public ExcelReaderSheetBuilder sheetName(String sheetName) { + readSheet.setSheetName(sheetName); + return this; + } + + public ReadSheet build() { + return readSheet; + } + + /** + * Sax read + */ + public void doRead() { + if (excelReader == null) { + throw new ExcelGenerateException("Must use 'EasyExcelFactory.read().sheet()' to call this method"); + } + excelReader.read(build()); + excelReader.finish(); + } + + /** + * Synchronous reads return results + * + * @return + */ + public List doReadSync() { + if (excelReader == null) { + throw new ExcelAnalysisException("Must use 'EasyExcelFactory.read().sheet()' to call this method"); + } + SyncReadListener syncReadListener = new SyncReadListener(); + registerReadListener(syncReadListener); + excelReader.read(build()); + excelReader.finish(); + return (List)syncReadListener.getList(); + } + + @Override + protected ReadSheet parameter() { + return readSheet; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/IgnoreExceptionReadListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/IgnoreExceptionReadListener.java new file mode 100644 index 0000000..b1c7948 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/IgnoreExceptionReadListener.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.read.listener; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * Interface to listen for read results + * + * @author Jiaju Zhuang + */ +public interface IgnoreExceptionReadListener extends ReadListener { + + /** + * All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the + * entire read will terminate. + * + * @param exception + * @param context + * @throws Exception + */ + @Override + default void onException(Exception exception, AnalysisContext context) throws Exception {} + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ModelBuildEventListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ModelBuildEventListener.java new file mode 100644 index 0000000..0546f71 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ModelBuildEventListener.java @@ -0,0 +1,158 @@ +package ai.chat2db.excel.read.listener; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Map; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.property.ExcelReadHeadProperty; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelDataConvertException; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.support.cglib.beans.BeanMap; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.ConverterUtils; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.MapUtils; + + +/** + * Convert to the object the user needs + * + * @author jipengfei + */ +public class ModelBuildEventListener implements IgnoreExceptionReadListener>> { + + @Override + public void invoke(Map> cellDataMap, AnalysisContext context) { + ReadSheetHolder readSheetHolder = context.readSheetHolder(); + if (HeadKindEnum.CLASS.equals(readSheetHolder.excelReadHeadProperty().getHeadKind())) { + context.readRowHolder() + .setCurrentRowAnalysisResult(buildUserModel(cellDataMap, readSheetHolder, context)); + return; + } + context.readRowHolder().setCurrentRowAnalysisResult(buildNoModel(cellDataMap, readSheetHolder, context)); + } + + private Object buildNoModel(Map> cellDataMap, ReadSheetHolder readSheetHolder, + AnalysisContext context) { + int index = 0; + Map map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size()); + for (Map.Entry> entry : cellDataMap.entrySet()) { + Integer key = entry.getKey(); + ReadCellData cellData = entry.getValue(); + while (index < key) { + map.put(index, null); + index++; + } + index++; + + ReadDefaultReturnEnum readDefaultReturn = context.readWorkbookHolder().getReadDefaultReturn(); + if (readDefaultReturn == ReadDefaultReturnEnum.STRING) { + // string + map.put(key, + (String) ConverterUtils.convertToJavaObject(cellData, null, null, readSheetHolder.converterMap(), + context, context.readRowHolder().getRowIndex(), key)); + } else { + // retrun ReadCellData + ReadCellData convertedReadCellData = convertReadCellData(cellData, + context.readWorkbookHolder().getReadDefaultReturn(), readSheetHolder, context, key); + if (readDefaultReturn == ReadDefaultReturnEnum.READ_CELL_DATA) { + map.put(key, convertedReadCellData); + } else { + map.put(key, convertedReadCellData.getData()); + } + } + } + // fix https://github.com/alibaba/easyexcel/issues/2014 + int headSize = calculateHeadSize(readSheetHolder); + while (index < headSize) { + map.put(index, null); + index++; + } + return map; + } + + private ReadCellData convertReadCellData(ReadCellData cellData, ReadDefaultReturnEnum readDefaultReturn, + ReadSheetHolder readSheetHolder, AnalysisContext context, Integer columnIndex) { + Class classGeneric; + switch (cellData.getType()) { + case STRING: + case DIRECT_STRING: + case ERROR: + case EMPTY: + classGeneric = String.class; + break; + case BOOLEAN: + classGeneric = Boolean.class; + break; + case NUMBER: + DataFormatData dataFormatData = cellData.getDataFormatData(); + if (dataFormatData != null && DateUtils.isADateFormat(dataFormatData.getIndex(), + dataFormatData.getFormat())) { + classGeneric = LocalDateTime.class; + } else { + classGeneric = BigDecimal.class; + } + break; + default: + classGeneric = ConverterUtils.defaultClassGeneric; + break; + } + + return (ReadCellData)ConverterUtils.convertToJavaObject(cellData, null, ReadCellData.class, + classGeneric, null, readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), + columnIndex); + } + + private int calculateHeadSize(ReadSheetHolder readSheetHolder) { + if (readSheetHolder.excelReadHeadProperty().getHeadMap().size() > 0) { + return readSheetHolder.excelReadHeadProperty().getHeadMap().size(); + } + if (readSheetHolder.getMaxNotEmptyDataHeadSize() != null) { + return readSheetHolder.getMaxNotEmptyDataHeadSize(); + } + return 0; + } + + private Object buildUserModel(Map> cellDataMap, ReadSheetHolder readSheetHolder, + AnalysisContext context) { + ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty(); + Object resultModel; + try { + resultModel = excelReadHeadProperty.getHeadClazz().newInstance(); + } catch (Exception e) { + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0, + new ReadCellData<>(CellDataTypeEnum.EMPTY), null, + "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e); + } + Map headMap = excelReadHeadProperty.getHeadMap(); + BeanMap dataMap = BeanMapUtils.create(resultModel); + for (Map.Entry entry : headMap.entrySet()) { + Integer index = entry.getKey(); + Head head = entry.getValue(); + String fieldName = head.getFieldName(); + if (!cellDataMap.containsKey(index)) { + continue; + } + ReadCellData cellData = cellDataMap.get(index); + Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(), + ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(), + fieldName, readSheetHolder), readSheetHolder.converterMap(), context, + context.readRowHolder().getRowIndex(), index); + if (value != null) { + dataMap.put(fieldName, value); + } + } + return resultModel; + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/PageReadListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/PageReadListener.java new file mode 100644 index 0000000..b45a2b7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/PageReadListener.java @@ -0,0 +1,60 @@ +package ai.chat2db.excel.read.listener; + +import java.util.List; +import java.util.function.Consumer; + +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; + +import org.apache.commons.collections4.CollectionUtils; + +/** + * page read listener + * + * @author Jiaju Zhuang + */ +public class PageReadListener implements ReadListener { + /** + * Default single handle the amount of data + */ + public static int BATCH_COUNT = 100; + /** + * Temporary storage of data + */ + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + /** + * consumer + */ + private final Consumer> consumer; + + /** + * Single handle the amount of data + */ + private final int batchCount; + + public PageReadListener(Consumer> consumer) { + this(consumer, BATCH_COUNT); + } + + public PageReadListener(Consumer> consumer, int batchCount) { + this.consumer = consumer; + this.batchCount = batchCount; + } + + @Override + public void invoke(T data, AnalysisContext context) { + cachedDataList.add(data); + if (cachedDataList.size() >= batchCount) { + consumer.accept(cachedDataList); + cachedDataList = ListUtils.newArrayListWithExpectedSize(batchCount); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + if (CollectionUtils.isNotEmpty(cachedDataList)) { + consumer.accept(cachedDataList); + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ReadListener.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ReadListener.java new file mode 100644 index 0000000..37fa890 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/listener/ReadListener.java @@ -0,0 +1,68 @@ +package ai.chat2db.excel.read.listener; + +import java.util.Map; + +import ai.chat2db.excel.event.Listener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.metadata.data.ReadCellData; + +/** + * Interface to listen for read results + * + * @author Jiaju Zhuang + */ +public interface ReadListener extends Listener { + /** + * All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the + * entire read will terminate. + * + * @param exception + * @param context + * @throws Exception + */ + default void onException(Exception exception, AnalysisContext context) throws Exception { + throw exception; + } + + /** + * When analysis one head row trigger invoke function. + * + * @param headMap + * @param context + */ + default void invokeHead(Map> headMap, AnalysisContext context) {} + + /** + * When analysis one row trigger invoke function. + * + * @param data one row value. It is same as {@link AnalysisContext#readRowHolder()} + * @param context analysis context + */ + void invoke(T data, AnalysisContext context); + + /** + * The current method is called when extra information is returned + * + * @param extra extra information + * @param context analysis context + */ + default void extra(CellExtra extra, AnalysisContext context) {} + + /** + * if have something to do after all analysis + * + * @param context + */ + void doAfterAllAnalysed(AnalysisContext context); + + /** + * Verify that there is another piece of data.You can stop the read by returning false + * + * @param context + * @return + */ + default boolean hasNext(AnalysisContext context) { + return true; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadBasicParameter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadBasicParameter.java new file mode 100644 index 0000000..88208bd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadBasicParameter.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.read.metadata; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.metadata.BasicParameter; +import ai.chat2db.excel.read.listener.ReadListener; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Read basic parameter + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ReadBasicParameter extends BasicParameter { + /** + * Count the number of added heads when read sheet. + * + *

+ * 0 - This Sheet has no head ,since the first row are the data + *

+ * 1 - This Sheet has one row head , this is the default + *

+ * 2 - This Sheet has two row head ,since the third row is the data + */ + private Integer headRowNumber; + /** + * Custom type listener run after default + */ + private List> customReadListenerList; + + public ReadBasicParameter() { + customReadListenerList = new ArrayList<>(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadSheet.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadSheet.java new file mode 100644 index 0000000..527c244 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadSheet.java @@ -0,0 +1,62 @@ +package ai.chat2db.excel.read.metadata; + +/** + * Read sheet + * + * @author jipengfei + */ +public class ReadSheet extends ReadBasicParameter { + /** + * Starting from 0 + */ + private Integer sheetNo; + /** + * sheet name + */ + private String sheetName; + + public ReadSheet() {} + + public ReadSheet(Integer sheetNo) { + this.sheetNo = sheetNo; + } + + public ReadSheet(Integer sheetNo, String sheetName) { + this.sheetNo = sheetNo; + this.sheetName = sheetName; + } + + public Integer getSheetNo() { + return sheetNo; + } + + public void setSheetNo(Integer sheetNo) { + this.sheetNo = sheetNo; + } + + public String getSheetName() { + return sheetName; + } + + public void setSheetName(String sheetName) { + this.sheetName = sheetName; + } + + public void copyBasicParameter(ReadSheet other) { + if (other == null) { + return; + } + this.setHeadRowNumber(other.getHeadRowNumber()); + this.setCustomReadListenerList(other.getCustomReadListenerList()); + this.setHead(other.getHead()); + this.setClazz(other.getClazz()); + this.setCustomConverterList(other.getCustomConverterList()); + this.setAutoTrim(other.getAutoTrim()); + this.setUse1904windowing(other.getUse1904windowing()); + } + + @Override + public String toString() { + return "ReadSheet{" + "sheetNo=" + sheetNo + ", sheetName='" + sheetName + '\'' + "} " + super.toString(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadWorkbook.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadWorkbook.java new file mode 100644 index 0000000..9653077 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/ReadWorkbook.java @@ -0,0 +1,118 @@ +package ai.chat2db.excel.read.metadata; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Set; + +import javax.xml.parsers.SAXParserFactory; + +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.cache.selector.ReadCacheSelector; +import ai.chat2db.excel.cache.selector.SimpleReadCacheSelector; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.read.listener.ModelBuildEventListener; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Workbook + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ReadWorkbook extends ReadBasicParameter { + /** + * Excel type + */ + private ExcelTypeEnum excelType; + /** + * Read InputStream + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private InputStream inputStream; + /** + * Read file + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private File file; + /** + * charset. + * Only work on the CSV file + */ + private Charset charset; + /** + * Mandatory use 'inputStream' .Default is false. + *

+ * if false, Will transfer 'inputStream' to temporary files to improve efficiency + */ + private Boolean mandatoryUseInputStream; + /** + * Default true + */ + private Boolean autoCloseStream; + /** + * This object can be read in the Listener {@link AnalysisEventListener#invoke(Object, AnalysisContext)} + * {@link AnalysisContext#getCustom()} + */ + private Object customObject; + /** + * A cache that stores temp data to save memory. + */ + private ReadCache readCache; + /** + * Ignore empty rows.Default is true. + */ + private Boolean ignoreEmptyRow; + /** + * Select the cache.Default use {@link SimpleReadCacheSelector} + */ + private ReadCacheSelector readCacheSelector; + /** + * Whether the encryption + */ + private String password; + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + */ + private String xlsxSAXParserFactoryName; + /** + * Whether to use the default listener, which is used by default. + *

+ * The {@link ModelBuildEventListener} is loaded by default to convert the object. + * defualt is true. + */ + private Boolean useDefaultListener; + + /** + * Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type. + * Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`. + * + * @see ReadDefaultReturnEnum + */ + private ReadDefaultReturnEnum readDefaultReturn; + + /** + * Read some additional fields. None are read by default. + * + * @see CellExtraTypeEnum + */ + private Set extraReadSet; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/AbstractReadHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/AbstractReadHolder.java new file mode 100644 index 0000000..efba0b6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/AbstractReadHolder.java @@ -0,0 +1,122 @@ +package ai.chat2db.excel.read.metadata.holder; + +import java.util.HashMap; +import java.util.List; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.read.metadata.property.ExcelReadHeadProperty; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; +import ai.chat2db.excel.converters.DefaultConverterLoader; +import ai.chat2db.excel.metadata.AbstractHolder; +import ai.chat2db.excel.read.listener.ModelBuildEventListener; +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.read.metadata.ReadBasicParameter; +import ai.chat2db.excel.read.metadata.ReadWorkbook; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Read Holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public abstract class AbstractReadHolder extends AbstractHolder implements ReadHolder { + /** + * Count the number of added heads when read sheet. + * + *

+ * 0 - This Sheet has no head ,since the first row are the data + *

+ * 1 - This Sheet has one row head , this is the default + *

+ * 2 - This Sheet has two row head ,since the third row is the data + */ + private Integer headRowNumber; + /** + * Excel head property + */ + private ExcelReadHeadProperty excelReadHeadProperty; + /** + * Read listener + */ + private List> readListenerList; + + public AbstractReadHolder(ReadBasicParameter readBasicParameter, AbstractReadHolder parentAbstractReadHolder) { + super(readBasicParameter, parentAbstractReadHolder); + + if (readBasicParameter.getUseScientificFormat() == null) { + if (parentAbstractReadHolder != null) { + getGlobalConfiguration().setUseScientificFormat( + parentAbstractReadHolder.getGlobalConfiguration().getUseScientificFormat()); + } + } else { + getGlobalConfiguration().setUseScientificFormat(readBasicParameter.getUseScientificFormat()); + } + + // Initialization property + this.excelReadHeadProperty = new ExcelReadHeadProperty(this, getClazz(), getHead()); + if (readBasicParameter.getHeadRowNumber() == null) { + if (parentAbstractReadHolder == null) { + if (excelReadHeadProperty.hasHead()) { + this.headRowNumber = excelReadHeadProperty.getHeadRowNumber(); + } else { + this.headRowNumber = 1; + } + } else { + this.headRowNumber = parentAbstractReadHolder.getHeadRowNumber(); + } + } else { + this.headRowNumber = readBasicParameter.getHeadRowNumber(); + } + + if (parentAbstractReadHolder == null) { + this.readListenerList = ListUtils.newArrayList(); + } else { + this.readListenerList = ListUtils.newArrayList(parentAbstractReadHolder.getReadListenerList()); + } + if (HolderEnum.WORKBOOK.equals(holderType())) { + Boolean useDefaultListener = ((ReadWorkbook)readBasicParameter).getUseDefaultListener(); + if (useDefaultListener == null || useDefaultListener) { + readListenerList.add(new ModelBuildEventListener()); + } + } + if (readBasicParameter.getCustomReadListenerList() != null + && !readBasicParameter.getCustomReadListenerList().isEmpty()) { + this.readListenerList.addAll(readBasicParameter.getCustomReadListenerList()); + } + + if (parentAbstractReadHolder == null) { + setConverterMap(DefaultConverterLoader.loadDefaultReadConverter()); + } else { + setConverterMap(new HashMap<>(parentAbstractReadHolder.getConverterMap())); + } + if (readBasicParameter.getCustomConverterList() != null + && !readBasicParameter.getCustomConverterList().isEmpty()) { + for (Converter converter : readBasicParameter.getCustomConverterList()) { + getConverterMap().put( + ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), + converter); + } + } + } + + @Override + public List> readListenerList() { + return getReadListenerList(); + } + + @Override + public ExcelReadHeadProperty excelReadHeadProperty() { + return getExcelReadHeadProperty(); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadHolder.java new file mode 100644 index 0000000..e26f1fd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadHolder.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.read.metadata.holder; + +import java.util.List; + +import ai.chat2db.excel.read.metadata.property.ExcelReadHeadProperty; +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.read.listener.ReadListener; + +/** + * + * Get the corresponding Holder + * + * @author Jiaju Zhuang + **/ +public interface ReadHolder extends ConfigurationHolder { + /** + * What handler does the currently operated cell need to execute + * + * @return Current {@link ReadListener} + */ + List> readListenerList(); + + /** + * What {@link ExcelReadHeadProperty} does the currently operated cell need to execute + * + * @return Current {@link ExcelReadHeadProperty} + */ + ExcelReadHeadProperty excelReadHeadProperty(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadRowHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadRowHolder.java new file mode 100644 index 0000000..9263d6e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadRowHolder.java @@ -0,0 +1,90 @@ +package ai.chat2db.excel.read.metadata.holder; + +import java.util.Map; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.Holder; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +public class ReadRowHolder implements Holder { + /** + * Returns row index of a row in the sheet that contains this cell.Start form 0. + */ + private Integer rowIndex; + /** + * Row type + */ + private RowTypeEnum rowType; + /** + * Cell map + */ + private Map cellMap; + /** + * The result of the previous listener + */ + private Object currentRowAnalysisResult; + /** + * Some global variables + */ + private GlobalConfiguration globalConfiguration; + + public ReadRowHolder(Integer rowIndex, RowTypeEnum rowType, GlobalConfiguration globalConfiguration, + Map cellMap) { + this.rowIndex = rowIndex; + this.rowType = rowType; + this.globalConfiguration = globalConfiguration; + this.cellMap = cellMap; + } + + public GlobalConfiguration getGlobalConfiguration() { + return globalConfiguration; + } + + public void setGlobalConfiguration(GlobalConfiguration globalConfiguration) { + this.globalConfiguration = globalConfiguration; + } + + public Object getCurrentRowAnalysisResult() { + return currentRowAnalysisResult; + } + + public void setCurrentRowAnalysisResult(Object currentRowAnalysisResult) { + this.currentRowAnalysisResult = currentRowAnalysisResult; + } + + public Integer getRowIndex() { + return rowIndex; + } + + public void setRowIndex(Integer rowIndex) { + this.rowIndex = rowIndex; + } + + public RowTypeEnum getRowType() { + return rowType; + } + + public void setRowType(RowTypeEnum rowType) { + this.rowType = rowType; + } + + public Map getCellMap() { + return cellMap; + } + + public void setCellMap(Map cellMap) { + this.cellMap = cellMap; + } + + @Override + public HolderEnum holderType() { + return HolderEnum.ROW; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadSheetHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadSheetHolder.java new file mode 100644 index 0000000..1e9da5f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadSheetHolder.java @@ -0,0 +1,101 @@ +package ai.chat2db.excel.read.metadata.holder; + +import java.util.LinkedHashMap; +import java.util.Map; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.metadata.Cell; +import ai.chat2db.excel.metadata.CellExtra; +import ai.chat2db.excel.metadata.data.ReadCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class ReadSheetHolder extends AbstractReadHolder { + + /** + * current param + */ + private ReadSheet readSheet; + /*** + * parent + */ + private ReadWorkbookHolder parentReadWorkbookHolder; + /*** + * sheetNo + */ + private Integer sheetNo; + /*** + * sheetName + */ + private String sheetName; + /** + * Gets the total number of rows , data may be inaccurate + */ + private Integer approximateTotalRowNumber; + /** + * Data storage of the current row. + */ + private Map cellMap; + /** + * Data storage of the current extra cell. + */ + private CellExtra cellExtra; + /** + * Index of the current row. + */ + private Integer rowIndex; + /** + * Current CellData + */ + private ReadCellData tempCellData; + /** + * Read the size of the largest head in sheet head data. + * see https://github.com/alibaba/easyexcel/issues/2014 + */ + private Integer maxNotEmptyDataHeadSize; + + /** + * Reading this sheet has ended. + */ + private Boolean ended; + + public ReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { + super(readSheet, readWorkbookHolder); + this.readSheet = readSheet; + this.parentReadWorkbookHolder = readWorkbookHolder; + this.sheetNo = readSheet.getSheetNo(); + this.sheetName = readSheet.getSheetName(); + this.cellMap = new LinkedHashMap<>(); + this.rowIndex = -1; + } + + /** + * Approximate total number of rows. + * use: getApproximateTotalRowNumber() + * + * @return + */ + @Deprecated + public Integer getTotal() { + return approximateTotalRowNumber; + } + + @Override + public HolderEnum holderType() { + return HolderEnum.SHEET; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadWorkbookHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadWorkbookHolder.java new file mode 100644 index 0000000..317aa22 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/ReadWorkbookHolder.java @@ -0,0 +1,197 @@ +package ai.chat2db.excel.read.metadata.holder; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import ai.chat2db.excel.cache.ReadCache; +import ai.chat2db.excel.cache.selector.EternalReadCacheSelector; +import ai.chat2db.excel.cache.selector.ReadCacheSelector; +import ai.chat2db.excel.cache.selector.SimpleReadCacheSelector; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Workbook holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class ReadWorkbookHolder extends AbstractReadHolder { + + /** + * current param + */ + private ReadWorkbook readWorkbook; + /** + * Read InputStream + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private InputStream inputStream; + /** + * Read file + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private File file; + + /** + * charset. + * Only work on the CSV file + */ + private Charset charset; + /** + * Mandatory use 'inputStream' .Default is false. + *

+ * if false, Will transfer 'inputStream' to temporary files to improve efficiency + */ + private Boolean mandatoryUseInputStream; + + /** + * Default true + */ + private Boolean autoCloseStream; + + /** + * Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type. + * Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`. + * + * @see ReadDefaultReturnEnum + */ + private ReadDefaultReturnEnum readDefaultReturn; + + /** + * Excel type + */ + private ExcelTypeEnum excelType; + /** + * This object can be read in the Listener {@link AnalysisEventListener#invoke(Object, AnalysisContext)} + * {@link AnalysisContext#getCustom()} + */ + private Object customObject; + /** + * Ignore empty rows.Default is true. + */ + private Boolean ignoreEmptyRow; + /** + * A cache that stores temp data to save memory. + */ + private ReadCache readCache; + /** + * Select the cache.Default use {@link SimpleReadCacheSelector} + */ + private ReadCacheSelector readCacheSelector; + /** + * Temporary files when reading excel + */ + private File tempFile; + /** + * Whether the encryption + */ + private String password; + /** + * Read some additional fields. None are read by default. + * + * @see CellExtraTypeEnum + */ + private Set extraReadSet; + /** + * Actual sheet data + */ + private List actualSheetDataList; + /** + * Parameter sheet data + */ + private List parameterSheetDataList; + /** + * Read all + */ + private Boolean readAll; + + /** + * Prevent repeating sheet + */ + private Set hasReadSheet; + + public ReadWorkbookHolder(ReadWorkbook readWorkbook) { + super(readWorkbook, null); + this.readWorkbook = readWorkbook; + if (readWorkbook.getInputStream() != null) { + this.inputStream = readWorkbook.getInputStream(); + } + this.file = readWorkbook.getFile(); + + if (readWorkbook.getCharset() == null) { + this.charset = Charset.defaultCharset(); + } else { + this.charset = readWorkbook.getCharset(); + } + + if (readWorkbook.getMandatoryUseInputStream() == null) { + this.mandatoryUseInputStream = Boolean.FALSE; + } else { + this.mandatoryUseInputStream = readWorkbook.getMandatoryUseInputStream(); + } + if (readWorkbook.getAutoCloseStream() == null) { + this.autoCloseStream = Boolean.TRUE; + } else { + this.autoCloseStream = readWorkbook.getAutoCloseStream(); + } + + if (readWorkbook.getReadDefaultReturn() == null) { + this.readDefaultReturn = ReadDefaultReturnEnum.STRING; + } else { + this.readDefaultReturn = readWorkbook.getReadDefaultReturn(); + } + + this.customObject = readWorkbook.getCustomObject(); + if (readWorkbook.getIgnoreEmptyRow() == null) { + this.ignoreEmptyRow = Boolean.TRUE; + } else { + this.ignoreEmptyRow = readWorkbook.getIgnoreEmptyRow(); + } + if (readWorkbook.getReadCache() != null) { + if (readWorkbook.getReadCacheSelector() != null) { + throw new ExcelAnalysisException("'readCache' and 'readCacheSelector' only one choice."); + } + this.readCacheSelector = new EternalReadCacheSelector(readWorkbook.getReadCache()); + } else { + if (readWorkbook.getReadCacheSelector() == null) { + this.readCacheSelector = new SimpleReadCacheSelector(); + } else { + this.readCacheSelector = readWorkbook.getReadCacheSelector(); + } + } + if (readWorkbook.getExtraReadSet() == null) { + this.extraReadSet = new HashSet(); + } else { + this.extraReadSet = readWorkbook.getExtraReadSet(); + } + this.hasReadSheet = new HashSet(); + this.password = readWorkbook.getPassword(); + } + + @Override + public HolderEnum holderType() { + return HolderEnum.WORKBOOK; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadSheetHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadSheetHolder.java new file mode 100644 index 0000000..196dd1a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadSheetHolder.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.read.metadata.holder.csv; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvReadSheetHolder extends ReadSheetHolder { + + public CsvReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { + super(readSheet, readWorkbookHolder); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadWorkbookHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadWorkbookHolder.java new file mode 100644 index 0000000..1128677 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/csv/CsvReadWorkbookHolder.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.read.metadata.holder.csv; + +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; + +/** + * Workbook holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvReadWorkbookHolder extends ReadWorkbookHolder { + + private CSVFormat csvFormat; + private CSVParser csvParser; + + public CsvReadWorkbookHolder(ReadWorkbook readWorkbook) { + super(readWorkbook); + setExcelType(ExcelTypeEnum.CSV); + this.csvFormat = CSVFormat.DEFAULT; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadSheetHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadSheetHolder.java new file mode 100644 index 0000000..2049008 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadSheetHolder.java @@ -0,0 +1,44 @@ +package ai.chat2db.excel.read.metadata.holder.xls; + +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class XlsReadSheetHolder extends ReadSheetHolder { + /** + * Row type.Temporary storage, last set in ReadRowHolder. + */ + private RowTypeEnum tempRowType; + /** + * Temp object index. + */ + private Integer tempObjectIndex; + /** + * Temp object index. + */ + private Map objectCacheMap; + + public XlsReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { + super(readSheet, readWorkbookHolder); + tempRowType = RowTypeEnum.EMPTY; + objectCacheMap = new HashMap(16); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadWorkbookHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadWorkbookHolder.java new file mode 100644 index 0000000..6f129ce --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xls/XlsReadWorkbookHolder.java @@ -0,0 +1,74 @@ +package ai.chat2db.excel.read.metadata.holder.xls; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +import ai.chat2db.excel.support.ExcelTypeEnum; + +/** + * Workbook holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class XlsReadWorkbookHolder extends ReadWorkbookHolder { + /** + * File System + */ + private POIFSFileSystem poifsFileSystem; + /** + * Format tracking HSSFListener + */ + private FormatTrackingHSSFListener formatTrackingHSSFListener; + /** + * HSSFWorkbook + */ + private HSSFWorkbook hssfWorkbook; + /** + * Bound sheet record list. + */ + private List boundSheetRecordList; + /** + * Need read sheet. + */ + private Boolean needReadSheet; + /** + * Sheet Index + */ + private Integer readSheetIndex; + /** + * Ignore record. + */ + private Boolean ignoreRecord; + + /** + * Has the current sheet already stopped + */ + private Boolean currentSheetStopped; + + public XlsReadWorkbookHolder(ReadWorkbook readWorkbook) { + super(readWorkbook); + this.boundSheetRecordList = new ArrayList(); + this.needReadSheet = Boolean.TRUE; + setExcelType(ExcelTypeEnum.XLS); + if (getGlobalConfiguration().getUse1904windowing() == null) { + getGlobalConfiguration().setUse1904windowing(Boolean.FALSE); + } + ignoreRecord = Boolean.FALSE; + currentSheetStopped = Boolean.TRUE; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java new file mode 100644 index 0000000..780ea53 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.read.metadata.holder.xlsx; + +import java.util.Deque; +import java.util.LinkedList; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class XlsxReadSheetHolder extends ReadSheetHolder { + /** + * Record the label of the current operation to prevent NPE. + */ + private Deque tagDeque; + /** + * Current Column + */ + private Integer columnIndex; + /** + * Data for current label. + */ + private StringBuilder tempData; + /** + * Formula for current label. + */ + private StringBuilder tempFormula; + /** + * excel Relationship + */ + private PackageRelationshipCollection packageRelationshipCollection; + + public XlsxReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { + super(readSheet, readWorkbookHolder); + this.tagDeque = new LinkedList(); + packageRelationshipCollection + = ((XlsxReadWorkbookHolder)readWorkbookHolder).getPackageRelationshipCollectionMap().get( + readSheet.getSheetNo()); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java new file mode 100644 index 0000000..09695d3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java @@ -0,0 +1,84 @@ +package ai.chat2db.excel.read.metadata.holder.xlsx; + +import java.util.Map; + +import javax.xml.parsers.SAXParserFactory; + +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.constant.BuiltinFormats; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; + +/** + * Workbook holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class XlsxReadWorkbookHolder extends ReadWorkbookHolder { + /** + * Package + */ + private OPCPackage opcPackage; + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + */ + private String saxParserFactoryName; + /** + * Current style information + */ + private StylesTable stylesTable; + /** + * cache data format + */ + private Map dataFormatDataCache; + + /** + * excel Relationship, key: sheetNo value: PackageRelationshipCollection + */ + private Map packageRelationshipCollectionMap; + + public XlsxReadWorkbookHolder(ReadWorkbook readWorkbook) { + super(readWorkbook); + this.saxParserFactoryName = readWorkbook.getXlsxSAXParserFactoryName(); + setExcelType(ExcelTypeEnum.XLSX); + dataFormatDataCache = MapUtils.newHashMap(); + } + + public DataFormatData dataFormatData(int dateFormatIndexInteger) { + return dataFormatDataCache.computeIfAbsent(dateFormatIndexInteger, key -> { + DataFormatData dataFormatData = new DataFormatData(); + if (stylesTable == null) { + return null; + } + XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger); + if (xssfCellStyle == null) { + return null; + } + dataFormatData.setIndex(xssfCellStyle.getDataFormat()); + dataFormatData.setFormat(BuiltinFormats.getBuiltinFormat(dataFormatData.getIndex(), + xssfCellStyle.getDataFormatString(), globalConfiguration().getLocale())); + return dataFormatData; + }); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/property/ExcelReadHeadProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/property/ExcelReadHeadProperty.java new file mode 100644 index 0000000..ecd3ad2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/metadata/property/ExcelReadHeadProperty.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.read.metadata.property; + +import java.util.List; + +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.metadata.property.ExcelHeadProperty; + +/** + * Define the header attribute of excel + * + * @author jipengfei + */ +public class ExcelReadHeadProperty extends ExcelHeadProperty { + + public ExcelReadHeadProperty(ConfigurationHolder configurationHolder, Class headClazz, List> head) { + super(configurationHolder, headClazz, head); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/AnalysisEventProcessor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/AnalysisEventProcessor.java new file mode 100644 index 0000000..626700b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/AnalysisEventProcessor.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.read.processor; + +import ai.chat2db.excel.context.AnalysisContext; + +/** + * + * Event processor + * + * @author jipengfei + */ +public interface AnalysisEventProcessor { + /** + * Read extra information + * + * @param analysisContext + */ + void extra(AnalysisContext analysisContext); + + /** + * End row + * + * @param analysisContext + */ + void endRow(AnalysisContext analysisContext); + + /** + * Notify after all analysed + * + * @param analysisContext + * Analysis context + */ + void endSheet(AnalysisContext analysisContext); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/DefaultAnalysisEventProcessor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/DefaultAnalysisEventProcessor.java new file mode 100644 index 0000000..2b034f7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/read/processor/DefaultAnalysisEventProcessor.java @@ -0,0 +1,168 @@ +package ai.chat2db.excel.read.processor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.enums.RowTypeEnum; +import ai.chat2db.excel.read.metadata.holder.ReadRowHolder; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; +import ai.chat2db.excel.read.metadata.property.ExcelReadHeadProperty; +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.util.ConverterUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelAnalysisStopException; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.read.listener.ReadListener; + +import org.apache.commons.collections4.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Analysis event + * + * @author jipengfei + */ +public class DefaultAnalysisEventProcessor implements AnalysisEventProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAnalysisEventProcessor.class); + + @Override + public void extra(AnalysisContext analysisContext) { + dealExtra(analysisContext); + } + + @Override + public void endRow(AnalysisContext analysisContext) { + if (RowTypeEnum.EMPTY.equals(analysisContext.readRowHolder().getRowType())) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Empty row!"); + } + if (analysisContext.readWorkbookHolder().getIgnoreEmptyRow()) { + return; + } + } + dealData(analysisContext); + } + + @Override + public void endSheet(AnalysisContext analysisContext) { + ReadSheetHolder readSheetHolder = analysisContext.readSheetHolder(); + if (BooleanUtils.isTrue(readSheetHolder.getEnded())) { + return; + } + readSheetHolder.setEnded(Boolean.TRUE); + + for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { + readListener.doAfterAllAnalysed(analysisContext); + } + } + + private void dealExtra(AnalysisContext analysisContext) { + for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { + try { + readListener.extra(analysisContext.readSheetHolder().getCellExtra(), analysisContext); + } catch (Exception e) { + onException(analysisContext, e); + break; + } + if (!readListener.hasNext(analysisContext)) { + throw new ExcelAnalysisStopException(); + } + } + } + + private void onException(AnalysisContext analysisContext, Exception e) { + for (ReadListener readListenerException : analysisContext.currentReadHolder().readListenerList()) { + try { + readListenerException.onException(e, analysisContext); + } catch (RuntimeException re) { + throw re; + } catch (Exception e1) { + throw new ExcelAnalysisException(e1.getMessage(), e1); + } + } + } + + private void dealData(AnalysisContext analysisContext) { + ReadRowHolder readRowHolder = analysisContext.readRowHolder(); + Map> cellDataMap = (Map)readRowHolder.getCellMap(); + readRowHolder.setCurrentRowAnalysisResult(cellDataMap); + int rowIndex = readRowHolder.getRowIndex(); + int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber(); + + boolean isData = rowIndex >= currentHeadRowNumber; + + // Last head column + if (!isData && currentHeadRowNumber == rowIndex + 1) { + buildHead(analysisContext, cellDataMap); + } + // Now is data + for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { + try { + if (isData) { + readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext); + } else { + readListener.invokeHead(cellDataMap, analysisContext); + } + } catch (Exception e) { + onException(analysisContext, e); + break; + } + if (!readListener.hasNext(analysisContext)) { + throw new ExcelAnalysisStopException(); + } + } + } + + private void buildHead(AnalysisContext analysisContext, Map> cellDataMap) { + // Rule out empty head, and then take the largest column + if (MapUtils.isNotEmpty(cellDataMap)) { + cellDataMap.entrySet() + .stream() + .filter(entry -> CellDataTypeEnum.EMPTY != entry.getValue().getType()) + .forEach(entry -> analysisContext.readSheetHolder().setMaxNotEmptyDataHeadSize(entry.getKey())); + } + + if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) { + return; + } + Map dataMap = ConverterUtils.convertToStringMap(cellDataMap, analysisContext); + ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty(); + Map headMapData = excelHeadPropertyData.getHeadMap(); + Map tmpHeadMap = new HashMap(headMapData.size() * 4 / 3 + 1); + for (Map.Entry entry : headMapData.entrySet()) { + Head headData = entry.getValue(); + if (headData.getForceIndex() || !headData.getForceName()) { + tmpHeadMap.put(entry.getKey(), headData); + continue; + } + List headNameList = headData.getHeadNameList(); + String headName = headNameList.get(headNameList.size() - 1); + for (Map.Entry stringEntry : dataMap.entrySet()) { + if (stringEntry == null) { + continue; + } + String headString = stringEntry.getValue(); + Integer stringKey = stringEntry.getKey(); + if (StringUtils.isEmpty(headString)) { + continue; + } + if (analysisContext.currentReadHolder().globalConfiguration().getAutoTrim()) { + headString = headString.trim(); + } + if (headName.equals(headString)) { + headData.setColumnIndex(stringKey); + tmpHeadMap.put(stringKey, headData); + break; + } + } + } + excelHeadPropertyData.setHeadMap(tmpHeadMap); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/support/ExcelTypeEnum.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/support/ExcelTypeEnum.java new file mode 100644 index 0000000..ca2307c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/support/ExcelTypeEnum.java @@ -0,0 +1,118 @@ +package ai.chat2db.excel.support; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelCommonException; +import ai.chat2db.excel.read.metadata.ReadWorkbook; +import ai.chat2db.excel.util.StringUtils; + +import lombok.Getter; +import org.apache.poi.util.IOUtils; + +/** + * @author jipengfei + */ +@Getter +public enum ExcelTypeEnum { + + /** + * csv + */ + CSV(".csv", new byte[] {-27, -89, -109, -27}), + + /** + * xls + */ + XLS(".xls", new byte[] {-48, -49, 17, -32, -95, -79, 26, -31}), + + /** + * xlsx + */ + XLSX(".xlsx", new byte[] {80, 75, 3, 4}); + + final String value; + final byte[] magic; + + ExcelTypeEnum(String value, byte[] magic) { + this.value = value; + this.magic = magic; + } + + final static int MAX_PATTERN_LENGTH = 8; + + public static ExcelTypeEnum valueOf(ReadWorkbook readWorkbook) { + ExcelTypeEnum excelType = readWorkbook.getExcelType(); + if (excelType != null) { + return excelType; + } + File file = readWorkbook.getFile(); + InputStream inputStream = readWorkbook.getInputStream(); + if (file == null && inputStream == null) { + throw new ExcelAnalysisException("File and inputStream must be a non-null."); + } + try { + if (file != null) { + if (!file.exists()) { + throw new ExcelAnalysisException("File " + file.getAbsolutePath() + " not exists."); + } + // If there is a password, use the FileMagic first + if (!StringUtils.isEmpty(readWorkbook.getPassword())) { + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) { + return recognitionExcelType(bufferedInputStream); + } + } + // Use the name to determine the type + String fileName = file.getName(); + if (fileName.endsWith(XLSX.getValue())) { + return XLSX; + } else if (fileName.endsWith(XLS.getValue())) { + return XLS; + } else if (fileName.endsWith(CSV.getValue())) { + return CSV; + } + if (StringUtils.isEmpty(readWorkbook.getPassword())) { + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) { + return recognitionExcelType(bufferedInputStream); + } + } + } + if (!inputStream.markSupported()) { + inputStream = new BufferedInputStream(inputStream); + readWorkbook.setInputStream(inputStream); + } + return recognitionExcelType(inputStream); + } catch (ExcelCommonException e) { + throw e; + } catch (Exception e) { + throw new ExcelCommonException( + "Convert excel format exception.You can try specifying the 'excelType' yourself", e); + } + } + + private static ExcelTypeEnum recognitionExcelType(InputStream inputStream) throws Exception { + // Grab the first bytes of this stream + byte[] data = IOUtils.peekFirstNBytes(inputStream, MAX_PATTERN_LENGTH); + if (findMagic(XLSX.magic, data)) { + return XLSX; + } else if (findMagic(XLS.magic, data)) { + return XLS; + } + // csv has no fixed prefix, if the format is not specified, it defaults to csv + return CSV; + } + + private static boolean findMagic(byte[] expected, byte[] actual) { + int i = 0; + for (byte expectedByte : expected) { + if (actual[i++] != expectedByte && expectedByte != '?') { + return false; + } + } + return true; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BeanMapUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BeanMapUtils.java new file mode 100644 index 0000000..068412f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BeanMapUtils.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.util; + + +import ai.chat2db.excel.support.cglib.beans.BeanMap; +import ai.chat2db.excel.support.cglib.core.DefaultNamingPolicy; + +/** + * bean utils + * + * @author Jiaju Zhuang + */ +public class BeanMapUtils { + + /** + * Helper method to create a new BeanMap. For finer + * control over the generated instance, use a new instance of + * BeanMap.Generator instead of this static method. + * + * Custom naming policy to prevent null pointer exceptions. + * see: https://github.com/alibaba/easyexcel/issues/2064 + * + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + public static BeanMap create(Object bean) { + BeanMap.Generator gen = new BeanMap.Generator(); + gen.setBean(bean); + gen.setContextClass(bean.getClass()); + gen.setNamingPolicy(EasyExcelNamingPolicy.INSTANCE); + return gen.create(); + } + + public static class EasyExcelNamingPolicy extends DefaultNamingPolicy { + public static final EasyExcelNamingPolicy INSTANCE = new EasyExcelNamingPolicy(); + + @Override + protected String getTag() { + return "ByEasyExcelCGLIB"; + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BooleanUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BooleanUtils.java new file mode 100644 index 0000000..31dd049 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/BooleanUtils.java @@ -0,0 +1,114 @@ +package ai.chat2db.excel.util; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class BooleanUtils { + + private static final String TRUE_NUMBER = "1"; + + private BooleanUtils() {} + + /** + * String to boolean + * + * @param str + * @return + */ + public static Boolean valueOf(String str) { + if (TRUE_NUMBER.equals(str)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + + // boolean Boolean methods + //----------------------------------------------------------------------- + /** + *

Checks if a {@code Boolean} value is {@code true}, + * handling {@code null} by returning {@code false}.

+ * + *
+     *   BooleanUtils.isTrue(Boolean.TRUE)  = true
+     *   BooleanUtils.isTrue(Boolean.FALSE) = false
+     *   BooleanUtils.isTrue(null)          = false
+     * 
+ * + * @param bool the boolean to check, null returns {@code false} + * @return {@code true} only if the input is non-null and true + * @since 2.1 + */ + public static boolean isTrue(final Boolean bool) { + return Boolean.TRUE.equals(bool); + } + + /** + *

Checks if a {@code Boolean} value is not {@code true}, + * handling {@code null} by returning {@code true}.

+ * + *
+     *   BooleanUtils.isNotTrue(Boolean.TRUE)  = false
+     *   BooleanUtils.isNotTrue(Boolean.FALSE) = true
+     *   BooleanUtils.isNotTrue(null)          = true
+     * 
+ * + * @param bool the boolean to check, null returns {@code true} + * @return {@code true} if the input is null or false + * @since 2.3 + */ + public static boolean isNotTrue(final Boolean bool) { + return !isTrue(bool); + } + + /** + *

Checks if a {@code Boolean} value is {@code false}, + * handling {@code null} by returning {@code false}.

+ * + *
+     *   BooleanUtils.isFalse(Boolean.TRUE)  = false
+     *   BooleanUtils.isFalse(Boolean.FALSE) = true
+     *   BooleanUtils.isFalse(null)          = false
+     * 
+ * + * @param bool the boolean to check, null returns {@code false} + * @return {@code true} only if the input is non-null and false + * @since 2.1 + */ + public static boolean isFalse(final Boolean bool) { + return Boolean.FALSE.equals(bool); + } + + /** + *

Checks if a {@code Boolean} value is not {@code false}, + * handling {@code null} by returning {@code true}.

+ * + *
+     *   BooleanUtils.isNotFalse(Boolean.TRUE)  = true
+     *   BooleanUtils.isNotFalse(Boolean.FALSE) = false
+     *   BooleanUtils.isNotFalse(null)          = true
+     * 
+ * + * @param bool the boolean to check, null returns {@code true} + * @return {@code true} if the input is null or true + * @since 2.3 + */ + public static boolean isNotFalse(final Boolean bool) { + return !isFalse(bool); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ClassUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ClassUtils.java new file mode 100644 index 0000000..8960c40 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ClassUtils.java @@ -0,0 +1,583 @@ +package ai.chat2db.excel.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelIgnoreUnannotated; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.annotation.write.style.ContentFontStyle; +import ai.chat2db.excel.annotation.write.style.ContentStyle; +import ai.chat2db.excel.converters.AutoConverter; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.exception.ExcelCommonException; +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.metadata.FieldCache; +import ai.chat2db.excel.metadata.FieldWrapper; +import ai.chat2db.excel.metadata.property.DateTimeFormatProperty; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.metadata.property.FontProperty; +import ai.chat2db.excel.metadata.property.NumberFormatProperty; +import ai.chat2db.excel.metadata.property.StyleProperty; +import ai.chat2db.excel.support.cglib.beans.BeanMap; +import ai.chat2db.excel.write.metadata.holder.WriteHolder; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class ClassUtils { + + /** + * memory cache + */ + public static final Map FIELD_CACHE = new ConcurrentHashMap<>(); + /** + * thread local cache + */ + private static final ThreadLocal> FIELD_THREAD_LOCAL = new ThreadLocal<>(); + + /** + * The cache configuration information for each of the class + */ + public static final ConcurrentHashMap, Map> CLASS_CONTENT_CACHE + = new ConcurrentHashMap<>(); + + /** + * The cache configuration information for each of the class + */ + private static final ThreadLocal, Map>> CLASS_CONTENT_THREAD_LOCAL + = new ThreadLocal<>(); + + /** + * The cache configuration information for each of the class + */ + public static final ConcurrentHashMap CONTENT_CACHE + = new ConcurrentHashMap<>(); + + /** + * The cache configuration information for each of the class + */ + private static final ThreadLocal> CONTENT_THREAD_LOCAL + = new ThreadLocal<>(); + + /** + * Calculate the configuration information for the class + * + * @param dataMap + * @param headClazz + * @param fieldName + * @return + */ + public static ExcelContentProperty declaredExcelContentProperty(Map dataMap, Class headClazz, + String fieldName, + ConfigurationHolder configurationHolder) { + Class clazz = null; + if (dataMap instanceof BeanMap) { + Object bean = ((BeanMap)dataMap).getBean(); + if (bean != null) { + clazz = bean.getClass(); + } + } + return getExcelContentProperty(clazz, headClazz, fieldName, configurationHolder); + } + + private static ExcelContentProperty getExcelContentProperty(Class clazz, Class headClass, String fieldName, + ConfigurationHolder configurationHolder) { + switch (configurationHolder.globalConfiguration().getFiledCacheLocation()) { + case THREAD_LOCAL: + Map contentCacheMap = CONTENT_THREAD_LOCAL.get(); + if (contentCacheMap == null) { + contentCacheMap = MapUtils.newHashMap(); + CONTENT_THREAD_LOCAL.set(contentCacheMap); + } + return contentCacheMap.computeIfAbsent(buildKey(clazz, headClass, fieldName), key -> { + return doGetExcelContentProperty(clazz, headClass, fieldName, configurationHolder); + }); + case MEMORY: + return CONTENT_CACHE.computeIfAbsent(buildKey(clazz, headClass, fieldName), key -> { + return doGetExcelContentProperty(clazz, headClass, fieldName, configurationHolder); + }); + case NONE: + return doGetExcelContentProperty(clazz, headClass, fieldName, configurationHolder); + default: + throw new UnsupportedOperationException("unsupported enum"); + } + } + + private static ExcelContentProperty doGetExcelContentProperty(Class clazz, Class headClass, + String fieldName, + ConfigurationHolder configurationHolder) { + ExcelContentProperty excelContentProperty = Optional.ofNullable( + declaredFieldContentMap(clazz, configurationHolder)) + .map(map -> map.get(fieldName)) + .orElse(null); + ExcelContentProperty headExcelContentProperty = Optional.ofNullable( + declaredFieldContentMap(headClass, configurationHolder)) + .map(map -> map.get(fieldName)) + .orElse(null); + ExcelContentProperty combineExcelContentProperty = new ExcelContentProperty(); + + combineExcelContentProperty(combineExcelContentProperty, headExcelContentProperty); + if (clazz != headClass) { + combineExcelContentProperty(combineExcelContentProperty, excelContentProperty); + } + return combineExcelContentProperty; + } + + public static void combineExcelContentProperty(ExcelContentProperty combineExcelContentProperty, + ExcelContentProperty excelContentProperty) { + if (excelContentProperty == null) { + return; + } + if (excelContentProperty.getField() != null) { + combineExcelContentProperty.setField(excelContentProperty.getField()); + } + if (excelContentProperty.getConverter() != null) { + combineExcelContentProperty.setConverter(excelContentProperty.getConverter()); + } + if (excelContentProperty.getDateTimeFormatProperty() != null) { + combineExcelContentProperty.setDateTimeFormatProperty(excelContentProperty.getDateTimeFormatProperty()); + } + if (excelContentProperty.getNumberFormatProperty() != null) { + combineExcelContentProperty.setNumberFormatProperty(excelContentProperty.getNumberFormatProperty()); + } + if (excelContentProperty.getContentStyleProperty() != null) { + combineExcelContentProperty.setContentStyleProperty(excelContentProperty.getContentStyleProperty()); + } + if (excelContentProperty.getContentFontProperty() != null) { + combineExcelContentProperty.setContentFontProperty(excelContentProperty.getContentFontProperty()); + } + } + + private static ContentPropertyKey buildKey(Class clazz, Class headClass, String fieldName) { + return new ContentPropertyKey(clazz, headClass, fieldName); + } + + private static Map declaredFieldContentMap(Class clazz, + ConfigurationHolder configurationHolder) { + if (clazz == null) { + return null; + } + switch (configurationHolder.globalConfiguration().getFiledCacheLocation()) { + case THREAD_LOCAL: + Map, Map> classContentCacheMap + = CLASS_CONTENT_THREAD_LOCAL.get(); + if (classContentCacheMap == null) { + classContentCacheMap = MapUtils.newHashMap(); + CLASS_CONTENT_THREAD_LOCAL.set(classContentCacheMap); + } + return classContentCacheMap.computeIfAbsent(clazz, key -> { + return doDeclaredFieldContentMap(clazz); + }); + case MEMORY: + return CLASS_CONTENT_CACHE.computeIfAbsent(clazz, key -> { + return doDeclaredFieldContentMap(clazz); + }); + case NONE: + return doDeclaredFieldContentMap(clazz); + default: + throw new UnsupportedOperationException("unsupported enum"); + } + + } + + private static Map doDeclaredFieldContentMap(Class clazz) { + if (clazz == null) { + return null; + } + List tempFieldList = new ArrayList<>(); + Class tempClass = clazz; + while (tempClass != null) { + Collections.addAll(tempFieldList, tempClass.getDeclaredFields()); + // Get the parent class and give it to yourself + tempClass = tempClass.getSuperclass(); + } + + ContentStyle parentContentStyle = clazz.getAnnotation(ContentStyle.class); + ContentFontStyle parentContentFontStyle = clazz.getAnnotation(ContentFontStyle.class); + Map fieldContentMap = MapUtils.newHashMapWithExpectedSize( + tempFieldList.size()); + for (Field field : tempFieldList) { + ExcelContentProperty excelContentProperty = new ExcelContentProperty(); + excelContentProperty.setField(field); + + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + if (excelProperty != null) { + Class> convertClazz = excelProperty.converter(); + if (convertClazz != AutoConverter.class) { + try { + Converter converter = convertClazz.getDeclaredConstructor().newInstance(); + excelContentProperty.setConverter(converter); + } catch (Exception e) { + throw new ExcelCommonException( + "Can not instance custom converter:" + convertClazz.getName()); + } + } + } + + ContentStyle contentStyle = field.getAnnotation(ContentStyle.class); + if (contentStyle == null) { + contentStyle = parentContentStyle; + } + excelContentProperty.setContentStyleProperty(StyleProperty.build(contentStyle)); + + ContentFontStyle contentFontStyle = field.getAnnotation(ContentFontStyle.class); + if (contentFontStyle == null) { + contentFontStyle = parentContentFontStyle; + } + excelContentProperty.setContentFontProperty(FontProperty.build(contentFontStyle)); + + excelContentProperty.setDateTimeFormatProperty( + DateTimeFormatProperty.build(field.getAnnotation(DateTimeFormat.class))); + excelContentProperty.setNumberFormatProperty( + NumberFormatProperty.build(field.getAnnotation(NumberFormat.class))); + + fieldContentMap.put(field.getName(), excelContentProperty); + } + return fieldContentMap; + } + + /** + * Parsing field in the class + * + * @param clazz Need to parse the class + * @param configurationHolder configuration + */ + public static FieldCache declaredFields(Class clazz, ConfigurationHolder configurationHolder) { + switch (configurationHolder.globalConfiguration().getFiledCacheLocation()) { + case THREAD_LOCAL: + Map fieldCacheMap = FIELD_THREAD_LOCAL.get(); + if (fieldCacheMap == null) { + fieldCacheMap = MapUtils.newHashMap(); + FIELD_THREAD_LOCAL.set(fieldCacheMap); + } + return fieldCacheMap.computeIfAbsent(new FieldCacheKey(clazz, configurationHolder), key -> { + return doDeclaredFields(clazz, configurationHolder); + }); + case MEMORY: + return FIELD_CACHE.computeIfAbsent(new FieldCacheKey(clazz, configurationHolder), key -> { + return doDeclaredFields(clazz, configurationHolder); + }); + case NONE: + return doDeclaredFields(clazz, configurationHolder); + default: + throw new UnsupportedOperationException("unsupported enum"); + } + } + + private static FieldCache doDeclaredFields(Class clazz, ConfigurationHolder configurationHolder) { + List tempFieldList = new ArrayList<>(); + Class tempClass = clazz; + // When the parent class is null, it indicates that the parent class (Object class) has reached the top + // level. + while (tempClass != null) { + Collections.addAll(tempFieldList, tempClass.getDeclaredFields()); + // Get the parent class and give it to yourself + tempClass = tempClass.getSuperclass(); + } + // Screening of field + Map> orderFieldMap = new TreeMap<>(); + Map indexFieldMap = new TreeMap<>(); + Set ignoreSet = new HashSet<>(); + + ExcelIgnoreUnannotated excelIgnoreUnannotated = clazz.getAnnotation(ExcelIgnoreUnannotated.class); + for (Field field : tempFieldList) { + declaredOneField(field, orderFieldMap, indexFieldMap, ignoreSet, excelIgnoreUnannotated); + } + Map sortedFieldMap = buildSortedAllFieldMap(orderFieldMap, indexFieldMap); + FieldCache fieldCache = new FieldCache(sortedFieldMap, indexFieldMap); + + if (!(configurationHolder instanceof WriteHolder)) { + return fieldCache; + } + + WriteHolder writeHolder = (WriteHolder)configurationHolder; + + boolean needIgnore = !CollectionUtils.isEmpty(writeHolder.excludeColumnFieldNames()) + || !CollectionUtils.isEmpty(writeHolder.excludeColumnIndexes()) + || !CollectionUtils.isEmpty(writeHolder.includeColumnFieldNames()) + || !CollectionUtils.isEmpty(writeHolder.includeColumnIndexes()); + + if (!needIgnore) { + return fieldCache; + } + // ignore filed + Map tempSortedFieldMap = MapUtils.newHashMap(); + int index = 0; + for (Map.Entry entry : sortedFieldMap.entrySet()) { + Integer key = entry.getKey(); + FieldWrapper field = entry.getValue(); + + // The current field needs to be ignored + if (writeHolder.ignore(field.getFieldName(), entry.getKey())) { + ignoreSet.add(field.getFieldName()); + indexFieldMap.remove(index); + } else { + // Mandatory sorted fields + if (indexFieldMap.containsKey(key)) { + tempSortedFieldMap.put(key, field); + } else { + // Need to reorder automatically + // Check whether the current key is already in use + while (tempSortedFieldMap.containsKey(index)) { + index++; + } + tempSortedFieldMap.put(index++, field); + } + } + } + fieldCache.setSortedFieldMap(tempSortedFieldMap); + + // resort field + resortField(writeHolder, fieldCache); + return fieldCache; + } + + /** + * it only works when {@link WriteHolder#includeColumnFieldNames()} or + * {@link WriteHolder#includeColumnIndexes()} has value + * and {@link WriteHolder#orderByIncludeColumn()} is true + **/ + private static void resortField(WriteHolder writeHolder, FieldCache fieldCache) { + if (!writeHolder.orderByIncludeColumn()) { + return; + } + Map indexFieldMap = fieldCache.getIndexFieldMap(); + + Collection includeColumnFieldNames = writeHolder.includeColumnFieldNames(); + if (!CollectionUtils.isEmpty(includeColumnFieldNames)) { + // Field sorted map + Map filedIndexMap = MapUtils.newHashMap(); + int fieldIndex = 0; + for (String includeColumnFieldName : includeColumnFieldNames) { + filedIndexMap.put(includeColumnFieldName, fieldIndex++); + } + + // rebuild sortedFieldMap + Map tempSortedFieldMap = MapUtils.newHashMap(); + fieldCache.getSortedFieldMap().forEach((index, field) -> { + Integer tempFieldIndex = filedIndexMap.get(field.getFieldName()); + if (tempFieldIndex != null) { + tempSortedFieldMap.put(tempFieldIndex, field); + + // The user has redefined the ordering and the ordering of annotations needs to be invalidated + if (!tempFieldIndex.equals(index)) { + indexFieldMap.remove(index); + } + } + }); + fieldCache.setSortedFieldMap(tempSortedFieldMap); + return; + } + + Collection includeColumnIndexes = writeHolder.includeColumnIndexes(); + if (!CollectionUtils.isEmpty(includeColumnIndexes)) { + // Index sorted map + Map filedIndexMap = MapUtils.newHashMap(); + int fieldIndex = 0; + for (Integer includeColumnIndex : includeColumnIndexes) { + filedIndexMap.put(includeColumnIndex, fieldIndex++); + } + + // rebuild sortedFieldMap + Map tempSortedFieldMap = MapUtils.newHashMap(); + fieldCache.getSortedFieldMap().forEach((index, field) -> { + Integer tempFieldIndex = filedIndexMap.get(index); + + // The user has redefined the ordering and the ordering of annotations needs to be invalidated + if (tempFieldIndex != null) { + tempSortedFieldMap.put(tempFieldIndex, field); + } + }); + fieldCache.setSortedFieldMap(tempSortedFieldMap); + } + } + + private static Map buildSortedAllFieldMap(Map> orderFieldMap, + Map indexFieldMap) { + + Map sortedAllFieldMap = new HashMap<>( + (orderFieldMap.size() + indexFieldMap.size()) * 4 / 3 + 1); + + Map tempIndexFieldMap = new HashMap<>(indexFieldMap); + int index = 0; + for (List fieldList : orderFieldMap.values()) { + for (FieldWrapper field : fieldList) { + while (tempIndexFieldMap.containsKey(index)) { + sortedAllFieldMap.put(index, tempIndexFieldMap.get(index)); + tempIndexFieldMap.remove(index); + index++; + } + sortedAllFieldMap.put(index, field); + index++; + } + } + sortedAllFieldMap.putAll(tempIndexFieldMap); + return sortedAllFieldMap; + } + + private static void declaredOneField(Field field, Map> orderFieldMap, + Map indexFieldMap, Set ignoreSet, + ExcelIgnoreUnannotated excelIgnoreUnannotated) { + String fieldName = FieldUtils.resolveCglibFieldName(field); + FieldWrapper fieldWrapper = new FieldWrapper(); + fieldWrapper.setField(field); + fieldWrapper.setFieldName(fieldName); + + ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); + + if (excelIgnore != null) { + ignoreSet.add(fieldName); + return; + } + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + boolean noExcelProperty = excelProperty == null && excelIgnoreUnannotated != null; + if (noExcelProperty) { + ignoreSet.add(fieldName); + return; + } + boolean isStaticFinalOrTransient = + (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) + || Modifier.isTransient(field.getModifiers()); + if (excelProperty == null && isStaticFinalOrTransient) { + ignoreSet.add(fieldName); + return; + } + // set heads + if (excelProperty != null) { + fieldWrapper.setHeads(excelProperty.value()); + } + + if (excelProperty != null && excelProperty.index() >= 0) { + if (indexFieldMap.containsKey(excelProperty.index())) { + throw new ExcelCommonException( + "The index of '" + indexFieldMap.get(excelProperty.index()).getFieldName() + + "' and '" + field.getName() + "' must be inconsistent"); + } + indexFieldMap.put(excelProperty.index(), fieldWrapper); + return; + } + + int order = Integer.MAX_VALUE; + if (excelProperty != null) { + order = excelProperty.order(); + } + List orderFieldList = orderFieldMap.computeIfAbsent(order, key -> ListUtils.newArrayList()); + orderFieldList.add(fieldWrapper); + } + + /** + *

Gets a {@code List} of all interfaces implemented by the given + * class and its superclasses.

+ * + *

The order is determined by looking through each interface in turn as + * declared in the source file and following its hierarchy up. Then each + * superclass is considered in the same way. Later duplicates are ignored, + * so the order is maintained.

+ * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of interfaces in order, + * {@code null} if null input + */ + public static List> getAllInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final LinkedHashSet> interfacesFound = new LinkedHashSet<>(); + getAllInterfaces(cls, interfacesFound); + + return new ArrayList<>(interfacesFound); + } + + /** + * Gets the interfaces for the specified class. + * + * @param cls the class to look up, may be {@code null} + * @param interfacesFound the {@code Set} of interfaces for the class + */ + private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { + while (cls != null) { + final Class[] interfaces = cls.getInterfaces(); + + for (final Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + + @Getter + @Setter + @EqualsAndHashCode + @AllArgsConstructor + public static class ContentPropertyKey { + private Class clazz; + private Class headClass; + private String fieldName; + } + + @Data + public static class FieldCacheKey { + private Class clazz; + private Collection excludeColumnFieldNames; + private Collection excludeColumnIndexes; + private Collection includeColumnFieldNames; + private Collection includeColumnIndexes; + + FieldCacheKey(Class clazz, ConfigurationHolder configurationHolder) { + this.clazz = clazz; + if (configurationHolder instanceof WriteHolder) { + WriteHolder writeHolder = (WriteHolder)configurationHolder; + this.excludeColumnFieldNames = writeHolder.excludeColumnFieldNames(); + this.excludeColumnIndexes = writeHolder.excludeColumnIndexes(); + this.includeColumnFieldNames = writeHolder.includeColumnFieldNames(); + this.includeColumnIndexes = writeHolder.includeColumnIndexes(); + } + } + } + + public static void removeThreadLocalCache() { + FIELD_THREAD_LOCAL.remove(); + CLASS_CONTENT_THREAD_LOCAL.remove(); + CONTENT_THREAD_LOCAL.remove(); + } +} + diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ConverterUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ConverterUtils.java new file mode 100644 index 0000000..376eff7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ConverterUtils.java @@ -0,0 +1,187 @@ +package ai.chat2db.excel.util; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; +import ai.chat2db.excel.converters.ConverterKeyBuild.ConverterKey; +import ai.chat2db.excel.converters.NullableObjectConverter; +import ai.chat2db.excel.converters.ReadConverterContext; +import ai.chat2db.excel.exception.ExcelDataConvertException; +import ai.chat2db.excel.metadata.data.CellData; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.read.metadata.holder.ReadSheetHolder; + +/** + * Converting objects + * + * @author Jiaju Zhuang + **/ +public class ConverterUtils { + public static Class defaultClassGeneric = String.class; + + private ConverterUtils() {} + + /** + * Convert it into a String map + * + * @param cellDataMap + * @param context + * @return + */ + public static Map convertToStringMap(Map> cellDataMap, + AnalysisContext context) { + Map stringMap = MapUtils.newHashMapWithExpectedSize(cellDataMap.size()); + ReadSheetHolder readSheetHolder = context.readSheetHolder(); + int index = 0; + for (Map.Entry> entry : cellDataMap.entrySet()) { + Integer key = entry.getKey(); + ReadCellData cellData = entry.getValue(); + while (index < key) { + stringMap.put(index, null); + index++; + } + index++; + if (cellData.getType() == CellDataTypeEnum.EMPTY) { + stringMap.put(key, null); + continue; + } + Converter converter = + readSheetHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); + if (converter == null) { + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, + "Converter not found, convert " + cellData.getType() + " to String"); + } + try { + stringMap.put(key, + (String)(converter.convertToJavaData(new ReadConverterContext<>(cellData, null, context)))); + } catch (Exception e) { + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, + "Convert data " + cellData + " to String error ", e); + } + } + return stringMap; + } + + /** + * Convert it into a Java object + * + * @param cellData + * @param field + * @param contentProperty + * @param converterMap + * @param context + * @param rowIndex + * @param columnIndex + * @return + */ + public static Object convertToJavaObject(ReadCellData cellData, Field field, + ExcelContentProperty contentProperty, Map> converterMap, AnalysisContext context, + Integer rowIndex, Integer columnIndex) { + return convertToJavaObject(cellData, field, null, null, contentProperty, converterMap, context, rowIndex, + columnIndex); + } + + /** + * Convert it into a Java object + * + * @param cellData + * @param field + * @param clazz + * @param contentProperty + * @param converterMap + * @param context + * @param rowIndex + * @param columnIndex + * @return + */ + public static Object convertToJavaObject(ReadCellData cellData, Field field, Class clazz, + Class classGeneric, ExcelContentProperty contentProperty, Map> converterMap, + AnalysisContext context, Integer rowIndex, Integer columnIndex) { + if (clazz == null) { + if (field == null) { + clazz = String.class; + } else { + clazz = field.getType(); + } + } + if (clazz == CellData.class || clazz == ReadCellData.class) { + ReadCellData cellDataReturn = cellData.clone(); + cellDataReturn.setData( + doConvertToJavaObject(cellData, getClassGeneric(field, classGeneric), contentProperty, + converterMap, context, rowIndex, columnIndex)); + return cellDataReturn; + } + return doConvertToJavaObject(cellData, clazz, contentProperty, converterMap, context, rowIndex, + columnIndex); + } + + private static Class getClassGeneric(Field field, Class classGeneric) { + if (classGeneric != null) { + return classGeneric; + } + if (field == null) { + return defaultClassGeneric; + } + Type type = field.getGenericType(); + if (!(type instanceof ParameterizedType)) { + return defaultClassGeneric; + } + ParameterizedType parameterizedType = (ParameterizedType)type; + Type[] types = parameterizedType.getActualTypeArguments(); + if (types == null || types.length == 0) { + return defaultClassGeneric; + } + Type actualType = types[0]; + if (!(actualType instanceof Class)) { + return defaultClassGeneric; + } + return (Class)actualType; + } + + /** + * @param cellData + * @param clazz + * @param contentProperty + * @param converterMap + * @param context + * @param rowIndex + * @param columnIndex + * @return + */ + private static Object doConvertToJavaObject(ReadCellData cellData, Class clazz, + ExcelContentProperty contentProperty, Map> converterMap, AnalysisContext context, + Integer rowIndex, Integer columnIndex) { + Converter converter = null; + if (contentProperty != null) { + converter = contentProperty.getConverter(); + } + + boolean canNotConverterEmpty = cellData.getType() == CellDataTypeEnum.EMPTY + && !(converter instanceof NullableObjectConverter); + if (canNotConverterEmpty) { + return null; + } + + if (converter == null) { + converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType())); + } + if (converter == null) { + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, + "Converter not found, convert " + cellData.getType() + " to " + clazz.getName()); + } + + try { + return converter.convertToJavaData(new ReadConverterContext<>(cellData, contentProperty, context)); + } catch (Exception e) { + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, + "Convert data " + cellData + " to " + clazz + " error ", e); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/DateUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/DateUtils.java new file mode 100644 index 0000000..e63c43a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/DateUtils.java @@ -0,0 +1,575 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package ai.chat2db.excel.util; + +import java.math.BigDecimal; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Pattern; + +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.util.LocaleUtil; + +/** + * Date utils + * + * @author Jiaju Zhuang + **/ +public class DateUtils { + /** + * Is a cache of dates + */ + private static final ThreadLocal> DATE_THREAD_LOCAL = + new ThreadLocal<>(); + /** + * Is a cache of dates + */ + private static final ThreadLocal> DATE_FORMAT_THREAD_LOCAL = + new ThreadLocal<>(); + + /** + * The following patterns are used in {@link #isADateFormat(Short, String)} + */ + private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]"); + private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]"); + private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]"); + // add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5 + private static final Pattern date_ptrn3b = + Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$"); + // elapsed time patterns: [h],[m] and [s] + private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]"); + // for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date + private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]"); + // for format which start with "年" or "月" or "日" or "时" or "分" or "秒" could be a Chinese date + private static final Pattern date_ptrn6 = Pattern.compile("(年|月|日|时|分|秒)+"); + + public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; + public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; + public static final String DATE_FORMAT_16 = "yyyy-MM-dd HH:mm"; + public static final String DATE_FORMAT_16_FORWARD_SLASH = "yyyy/MM/dd HH:mm"; + public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; + public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_FORMAT_19_FORWARD_SLASH = "yyyy/MM/dd HH:mm:ss"; + private static final String MINUS = "-"; + + public static String defaultDateFormat = DATE_FORMAT_19; + + public static String defaultLocalDateFormat = DATE_FORMAT_10; + + public static final int SECONDS_PER_MINUTE = 60; + public static final int MINUTES_PER_HOUR = 60; + public static final int HOURS_PER_DAY = 24; + public static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + // used to specify that date is invalid + private static final int BAD_DATE = -1; + public static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L; + + private DateUtils() {} + + /** + * convert string to date + * + * @param dateString + * @param dateFormat + * @return + * @throws ParseException + */ + public static Date parseDate(String dateString, String dateFormat) throws ParseException { + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = switchDateFormat(dateString); + } + return getCacheDateFormat(dateFormat).parse(dateString); + } + + /** + * convert string to date + * + * @param dateString + * @param dateFormat + * @param local + * @return + */ + public static LocalDateTime parseLocalDateTime(String dateString, String dateFormat, Locale local) { + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = switchDateFormat(dateString); + } + if (local == null) { + return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(dateFormat)); + } else { + return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(dateFormat, local)); + } + } + + /** + * convert string to date + * + * @param dateString + * @param dateFormat + * @param local + * @return + */ + public static LocalDate parseLocalDate(String dateString, String dateFormat, Locale local) { + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = switchDateFormat(dateString); + } + if (local == null) { + return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(dateFormat)); + } else { + return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(dateFormat, local)); + } + } + + /** + * convert string to date + * + * @param dateString + * @return + * @throws ParseException + */ + public static Date parseDate(String dateString) throws ParseException { + return parseDate(dateString, switchDateFormat(dateString)); + } + + /** + * switch date format + * + * @param dateString + * @return + */ + public static String switchDateFormat(String dateString) { + int length = dateString.length(); + switch (length) { + case 19: + if (dateString.contains(MINUS)) { + return DATE_FORMAT_19; + } else { + return DATE_FORMAT_19_FORWARD_SLASH; + } + case 16: + if (dateString.contains(MINUS)) { + return DATE_FORMAT_16; + } else { + return DATE_FORMAT_16_FORWARD_SLASH; + } + case 17: + return DATE_FORMAT_17; + case 14: + return DATE_FORMAT_14; + case 10: + return DATE_FORMAT_10; + default: + throw new IllegalArgumentException("can not find date format for:" + dateString); + } + } + + /** + * Format date + *

+ * yyyy-MM-dd HH:mm:ss + * + * @param date + * @return + */ + public static String format(Date date) { + return format(date, null); + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(Date date, String dateFormat) { + if (date == null) { + return null; + } + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = defaultDateFormat; + } + return getCacheDateFormat(dateFormat).format(date); + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(LocalDateTime date, String dateFormat, Locale local) { + if (date == null) { + return null; + } + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = defaultDateFormat; + } + if (local == null) { + return date.format(DateTimeFormatter.ofPattern(dateFormat)); + } else { + return date.format(DateTimeFormatter.ofPattern(dateFormat, local)); + } + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(LocalDate date, String dateFormat) { + return format(date, dateFormat, null); + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(LocalDate date, String dateFormat, Locale local) { + if (date == null) { + return null; + } + if (StringUtils.isEmpty(dateFormat)) { + dateFormat = defaultLocalDateFormat; + } + if (local == null) { + return date.format(DateTimeFormatter.ofPattern(dateFormat)); + } else { + return date.format(DateTimeFormatter.ofPattern(dateFormat, local)); + } + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(LocalDateTime date, String dateFormat) { + return format(date, dateFormat, null); + } + + /** + * Format date + * + * @param date + * @param dateFormat + * @return + */ + public static String format(BigDecimal date, Boolean use1904windowing, String dateFormat) { + if (date == null) { + return null; + } + LocalDateTime localDateTime = DateUtil.getLocalDateTime(date.doubleValue(), + BooleanUtils.isTrue(use1904windowing), true); + return format(localDateTime, dateFormat); + } + + private static DateFormat getCacheDateFormat(String dateFormat) { + Map dateFormatMap = DATE_FORMAT_THREAD_LOCAL.get(); + if (dateFormatMap == null) { + dateFormatMap = new HashMap(); + DATE_FORMAT_THREAD_LOCAL.set(dateFormatMap); + } else { + SimpleDateFormat dateFormatCached = dateFormatMap.get(dateFormat); + if (dateFormatCached != null) { + return dateFormatCached; + } + } + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); + dateFormatMap.put(dateFormat, simpleDateFormat); + return simpleDateFormat; + } + + /** + * Given an Excel date with either 1900 or 1904 date windowing, + * converts it to a java.util.Date. + * + * Excel Dates and Times are stored without any timezone + * information. If you know (through other means) that your file + * uses a different TimeZone to the system default, you can use + * this version of the getJavaDate() method to handle it. + * + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @return Java representation of the date, or null if date is not a valid Excel date + */ + public static Date getJavaDate(double date, boolean use1904windowing) { + Calendar calendar = getJavaCalendar(date, use1904windowing, null, true); + return calendar == null ? null : calendar.getTime(); + } + + /** + * Get EXCEL date as Java Calendar with given time zone. + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @param timeZone The TimeZone to evaluate the date in + * @param roundSeconds round to closest second + * @return Java representation of the date, or null if date is not a valid Excel date + */ + public static Calendar getJavaCalendar(double date, boolean use1904windowing, TimeZone timeZone, boolean roundSeconds) { + if (!isValidExcelDate(date)) { + return null; + } + int wholeDays = (int)Math.floor(date); + int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5); + Calendar calendar; + if (timeZone != null) { + calendar = LocaleUtil.getLocaleCalendar(timeZone); + } else { + calendar = LocaleUtil.getLocaleCalendar(); // using default time-zone + } + setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing, roundSeconds); + return calendar; + } + + + public static void setCalendar(Calendar calendar, int wholeDays, + int millisecondsInDay, boolean use1904windowing, boolean roundSeconds) { + int startYear = 1900; + int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't + if (use1904windowing) { + startYear = 1904; + dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day + } + else if (wholeDays < 61) { + // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists + // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation + dayAdjust = 0; + } + calendar.set(startYear, Calendar.JANUARY, wholeDays + dayAdjust, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, millisecondsInDay); + if (calendar.get(Calendar.MILLISECOND) == 0) { + calendar.clear(Calendar.MILLISECOND); + } + if (roundSeconds) { + // This is different from poi where you need to change 500 to 499 + calendar.add(Calendar.MILLISECOND, 499); + calendar.clear(Calendar.MILLISECOND); + } + } + + + /** + * Given a double, checks if it is a valid Excel date. + * + * @return true if valid + * @param value the double value + */ + + public static boolean isValidExcelDate(double value) + { + return (value > -Double.MIN_VALUE); + } + + + /** + * Given an Excel date with either 1900 or 1904 date windowing, + * converts it to a java.time.LocalDateTime. + * + * Excel Dates and Times are stored without any timezone + * information. If you know (through other means) that your file + * uses a different TimeZone to the system default, you can use + * this version of the getJavaDate() method to handle it. + * + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @return Java representation of the date, or null if date is not a valid Excel date + */ + public static LocalDateTime getLocalDateTime(double date, boolean use1904windowing) { + return DateUtil.getLocalDateTime(date, use1904windowing, true); + } + + /** + * Given an Excel date with either 1900 or 1904 date windowing, + * converts it to a java.time.LocalDate. + * + * Excel Dates and Times are stored without any timezone + * information. If you know (through other means) that your file + * uses a different TimeZone to the system default, you can use + * this version of the getJavaDate() method to handle it. + * + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @return Java representation of the date, or null if date is not a valid Excel date + */ + public static LocalDate getLocalDate(double date, boolean use1904windowing) { + LocalDateTime localDateTime = getLocalDateTime(date, use1904windowing); + return localDateTime == null ? null : localDateTime.toLocalDate(); + } + + /** + * Determine if it is a date format. + * + * @param formatIndex + * @param formatString + * @return + */ + public static boolean isADateFormat(Short formatIndex, String formatString) { + if (formatIndex == null) { + return false; + } + Map isDateCache = DATE_THREAD_LOCAL.get(); + if (isDateCache == null) { + isDateCache = MapUtils.newHashMap(); + DATE_THREAD_LOCAL.set(isDateCache); + } else { + Boolean isDatecachedDataList = isDateCache.get(formatIndex); + if (isDatecachedDataList != null) { + return isDatecachedDataList; + } + } + boolean isDate = isADateFormatUncached(formatIndex, formatString); + isDateCache.put(formatIndex, isDate); + return isDate; + } + + /** + * Determine if it is a date format. + * + * @param formatIndex + * @param formatString + * @return + */ + public static boolean isADateFormatUncached(Short formatIndex, String formatString) { + // First up, is this an internal date format? + if (isInternalDateFormat(formatIndex)) { + return true; + } + if (StringUtils.isEmpty(formatString)) { + return false; + } + String fs = formatString; + final int length = fs.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = fs.charAt(i); + if (i < length - 1) { + char nc = fs.charAt(i + 1); + if (c == '\\') { + switch (nc) { + case '-': + case ',': + case '.': + case ' ': + case '\\': + // skip current '\' and continue to the next char + continue; + } + } else if (c == ';' && nc == '@') { + i++; + // skip ";@" duplets + continue; + } + } + sb.append(c); + } + fs = sb.toString(); + + // short-circuit if it indicates elapsed time: [h], [m] or [s] + if (date_ptrn4.matcher(fs).matches()) { + return true; + } + // If it starts with [DBNum1] or [DBNum2] or [DBNum3] + // then it could be a Chinese date + fs = date_ptrn5.matcher(fs).replaceAll(""); + // If it starts with [$-...], then could be a date, but + // who knows what that starting bit is all about + fs = date_ptrn1.matcher(fs).replaceAll(""); + // If it starts with something like [Black] or [Yellow], + // then it could be a date + fs = date_ptrn2.matcher(fs).replaceAll(""); + // You're allowed something like dd/mm/yy;[red]dd/mm/yy + // which would place dates before 1900/1904 in red + // For now, only consider the first one + final int separatorIndex = fs.indexOf(';'); + if (0 < separatorIndex && separatorIndex < fs.length() - 1) { + fs = fs.substring(0, separatorIndex); + } + + // Ensure it has some date letters in it + // (Avoids false positives on the rest of pattern 3) + if (!date_ptrn3a.matcher(fs).find()) { + return false; + } + + // If we get here, check it's only made up, in any case, of: + // y m d h s - \ / , . : [ ] T + // optionally followed by AM/PM + boolean result = date_ptrn3b.matcher(fs).matches(); + if (result) { + return true; + } + result = date_ptrn6.matcher(fs).find(); + return result; + } + + /** + * Given a format ID this will check whether the format represents an internal excel date format or not. + * + * @see #isADateFormat(Short, String) + */ + public static boolean isInternalDateFormat(short format) { + switch (format) { + // Internal Date Formats as described on page 427 in + // Microsoft Excel Dev's Kit... + // 14-22 + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + // 45-47 + case 0x2d: + case 0x2e: + case 0x2f: + return true; + } + return false; + } + + public static void removeThreadLocalCache() { + DATE_THREAD_LOCAL.remove(); + DATE_FORMAT_THREAD_LOCAL.remove(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/EasyExcelTempFileCreationStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/EasyExcelTempFileCreationStrategy.java new file mode 100644 index 0000000..a254f01 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/EasyExcelTempFileCreationStrategy.java @@ -0,0 +1,136 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package ai.chat2db.excel.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileAttribute; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.poi.util.DefaultTempFileCreationStrategy; +import org.apache.poi.util.TempFileCreationStrategy; + +import static org.apache.poi.util.TempFile.JAVA_IO_TMPDIR; + +/** + * In the scenario where `poifiles` are cleaned up, the {@link DefaultTempFileCreationStrategy} will throw a + * java.nio.file.NoSuchFileException. Therefore, it is necessary to verify the existence of the temporary file every + * time it is created. + * + * @author Jiaju Zhuang + */ +public class EasyExcelTempFileCreationStrategy implements TempFileCreationStrategy { + /** + * Name of POI files directory in temporary directory. + */ + public static final String POIFILES = "poifiles"; + + /** + * To use files.deleteOnExit after clean JVM exit, set the -Dpoi.delete.tmp.files.on.exit JVM property + */ + public static final String DELETE_FILES_ON_EXIT = "poi.delete.tmp.files.on.exit"; + + /** + * The directory where the temporary files will be created (null to use the default directory). + */ + private volatile File dir; + + /** + * The lock to make dir initialized only once. + */ + private final Lock dirLock = new ReentrantLock(); + + /** + * Creates the strategy so that it creates the temporary files in the default directory. + * + * @see File#createTempFile(String, String) + */ + public EasyExcelTempFileCreationStrategy() { + this(null); + } + + /** + * Creates the strategy allowing to set the + * + * @param dir The directory where the temporary files will be created (null to use the default + * directory). + * @see Files#createTempFile(Path, String, String, FileAttribute[]) + */ + public EasyExcelTempFileCreationStrategy(File dir) { + this.dir = dir; + } + + private void createPOIFilesDirectory() throws IOException { + // Create our temp dir only once by double-checked locking + // The directory is not deleted, even if it was created by this TempFileCreationStrategy + if (dir == null || !dir.exists()) { + dirLock.lock(); + try { + if (dir == null || !dir.exists()) { + String tmpDir = System.getProperty(JAVA_IO_TMPDIR); + if (tmpDir == null) { + throw new IOException("System's temporary directory not defined - set the -D" + JAVA_IO_TMPDIR + + " jvm property!"); + } + Path dirPath = Paths.get(tmpDir, POIFILES); + dir = Files.createDirectories(dirPath).toFile(); + } + } finally { + dirLock.unlock(); + } + return; + } + } + + @Override + public File createTempFile(String prefix, String suffix) throws IOException { + // Identify and create our temp dir, if needed + createPOIFilesDirectory(); + + // Generate a unique new filename + File newFile = Files.createTempFile(dir.toPath(), prefix, suffix).toFile(); + + // Set the delete on exit flag, but only when explicitly disabled + if (System.getProperty(DELETE_FILES_ON_EXIT) != null) { + newFile.deleteOnExit(); + } + + // All done + return newFile; + } + + /* (non-JavaDoc) Created directory path is /poifiles/prefix0123456789 */ + @Override + public File createTempDirectory(String prefix) throws IOException { + // Identify and create our temp dir, if needed + createPOIFilesDirectory(); + + // Generate a unique new filename + File newDirectory = Files.createTempDirectory(dir.toPath(), prefix).toFile(); + + //this method appears to be only used in tests, so it is probably ok to use deleteOnExit + newDirectory.deleteOnExit(); + + // All done + return newDirectory; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FieldUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FieldUtils.java new file mode 100644 index 0000000..a4dd52b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FieldUtils.java @@ -0,0 +1,168 @@ +package ai.chat2db.excel.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +import ai.chat2db.excel.metadata.NullObject; +import ai.chat2db.excel.support.cglib.beans.BeanMap; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class FieldUtils { + + public static Class nullObjectClass = NullObject.class; + + private static final int START_RESOLVE_FIELD_LENGTH = 2; + + public static Class getFieldClass(Map dataMap, String fieldName, Object value) { + if (dataMap instanceof BeanMap) { + Class fieldClass = ((BeanMap)dataMap).getPropertyType(fieldName); + if (fieldClass != null) { + return fieldClass; + } + } + return getFieldClass(value); + } + + public static Class getFieldClass(Object value) { + if (value != null) { + return value.getClass(); + } + return nullObjectClass; + } + + /** + * Parsing the name matching cglib。 + *

+     *     null -> null
+     *     string1 -> string1
+     *     String2 -> string2
+     *     sTring3 -> STring3
+     *     STring4 -> STring4
+     *     STRING5 -> STRING5
+     *     STRing6 -> STRing6
+     * 
+ * + * @param field field + * @return field name. + */ + public static String resolveCglibFieldName(Field field) { + if (field == null) { + return null; + } + String fieldName = field.getName(); + if (StringUtils.isBlank(fieldName) || fieldName.length() < START_RESOLVE_FIELD_LENGTH) { + return fieldName; + } + char firstChar = fieldName.charAt(0); + char secondChar = fieldName.charAt(1); + if (Character.isUpperCase(firstChar) == Character.isUpperCase(secondChar)) { + return fieldName; + } + if (Character.isUpperCase(firstChar)) { + return buildFieldName(Character.toLowerCase(firstChar), fieldName); + } + return buildFieldName(Character.toUpperCase(firstChar), fieldName); + } + + private static String buildFieldName(char firstChar, String fieldName) { + return firstChar + fieldName.substring(1); + } + + /** + * Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces will be considered. + * + * @param cls the {@link Class} to reflect, must not be {@code null} + * @param fieldName the field name to obtain + * @return the Field object + * @throws IllegalArgumentException if the class is {@code null}, or the field name is blank or empty + */ + public static Field getField(final Class cls, final String fieldName) { + final Field field = getField(cls, fieldName, false); + MemberUtils.setAccessibleWorkaround(field); + return field; + } + + /** + * Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces will be + * considered. + * + * @param cls the {@link Class} to reflect, must not be {@code null} + * @param fieldName the field name to obtain + * @param forceAccess whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will + * only + * match {@code public} fields. + * @return the Field object + * @throws NullPointerException if the class is {@code null} + * @throws IllegalArgumentException if the field name is blank or empty or is matched at multiple places + * in the inheritance hierarchy + */ + public static Field getField(final Class cls, final String fieldName, final boolean forceAccess) { + Validate.isTrue(cls != null, "The class must not be null"); + Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); + // FIXME is this workaround still needed? lang requires Java 6 + // Sun Java 1.3 has a bugged implementation of getField hence we write the + // code ourselves + + // getField() will return the Field object with the declaring class + // set correctly to the class that declares the field. Thus requesting the + // field on a subclass will return the field from the superclass. + // + // priority order for lookup: + // searchclass private/protected/package/public + // superclass protected/package/public + // private/different package blocks access to further superclasses + // implementedinterface public + + // check up the superclass hierarchy + for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { + try { + final Field field = acls.getDeclaredField(fieldName); + // getDeclaredField checks for non-public scopes as well + // and it returns accurate results + if (!Modifier.isPublic(field.getModifiers())) { + if (forceAccess) { + field.setAccessible(true); + } else { + continue; + } + } + return field; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + // check the public interface case. This must be manually searched for + // incase there is a public supersuperclass field hidden by a private/package + // superclass field. + Field match = null; + for (final Class class1 : ClassUtils.getAllInterfaces(cls)) { + try { + final Field test = class1.getField(fieldName); + Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + + "; a matching field exists on two or more implemented interfaces.", fieldName, cls); + match = test; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + return match; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileTypeUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileTypeUtils.java new file mode 100644 index 0000000..dbfca35 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileTypeUtils.java @@ -0,0 +1,59 @@ +package ai.chat2db.excel.util; + +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.metadata.data.ImageData.ImageType; + +/** + * file type utils + * + * @author Jiaju Zhuang + */ +public class FileTypeUtils { + + private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', + 'f'}; + private static final int IMAGE_TYPE_MARK_LENGTH = 28; + + private static final Map FILE_TYPE_MAP; + + /** + * Default image type + */ + public static ImageType defaultImageType = ImageType.PICTURE_TYPE_PNG; + + static { + FILE_TYPE_MAP = new HashMap<>(); + FILE_TYPE_MAP.put("ffd8ff", ImageType.PICTURE_TYPE_JPEG); + FILE_TYPE_MAP.put("89504e47", ImageType.PICTURE_TYPE_PNG); + } + + public static int getImageTypeFormat(byte[] image) { + ImageType imageType = getImageType(image); + if (imageType != null) { + return imageType.getValue(); + } + return defaultImageType.getValue(); + } + + public static ImageType getImageType(byte[] image) { + if (image == null || image.length <= IMAGE_TYPE_MARK_LENGTH) { + return null; + } + byte[] typeMarkByte = new byte[IMAGE_TYPE_MARK_LENGTH]; + System.arraycopy(image, 0, typeMarkByte, 0, IMAGE_TYPE_MARK_LENGTH); + return FILE_TYPE_MAP.get(encodeHexStr(typeMarkByte)); + } + + private static String encodeHexStr(byte[] data) { + final int len = data.length; + final char[] out = new char[len << 1]; + // two characters from the hex value. + for (int i = 0, j = 0; i < len; i++) { + out[j++] = DIGITS[(0xF0 & data[i]) >>> 4]; + out[j++] = DIGITS[0x0F & data[i]]; + } + return new String(out); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileUtils.java new file mode 100644 index 0000000..3ff44aa --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/FileUtils.java @@ -0,0 +1,225 @@ +package ai.chat2db.excel.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; + +import ai.chat2db.excel.exception.ExcelAnalysisException; +import ai.chat2db.excel.exception.ExcelCommonException; + +import org.apache.poi.util.TempFile; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class FileUtils { + public static final String POI_FILES = "poifiles"; + public static final String EX_CACHE = "excache"; + /** + * If a server has multiple projects in use at the same time, a directory with the same name will be created under + * the temporary directory, but each project is run by a different user, so there is a permission problem, so each + * project creates a unique UUID as a separate Temporary Files. + */ + private static String tempFilePrefix = + System.getProperty(TempFile.JAVA_IO_TMPDIR) + File.separator + UUID.randomUUID().toString() + File.separator; + /** + * Used to store poi temporary files. + */ + private static String poiFilesPath = tempFilePrefix + POI_FILES + File.separator; + /** + * Used to store easy excel temporary files. + */ + private static String cachePath = tempFilePrefix + EX_CACHE + File.separator; + + private static final int WRITE_BUFF_SIZE = 8192; + + private FileUtils() {} + + static { + // Create a temporary directory in advance + File tempFile = new File(tempFilePrefix); + createDirectory(tempFile); + tempFile.deleteOnExit(); + // Initialize the cache directory + File cacheFile = new File(cachePath); + createDirectory(cacheFile); + } + + /** + * Reads the contents of a file into a byte array. * The file is always closed. + * + * @param file + * @return + * @throws IOException + */ + public static byte[] readFileToByteArray(final File file) throws IOException { + InputStream in = openInputStream(file); + try { + final long fileLength = file.length(); + return fileLength > 0 ? IoUtils.toByteArray(in, (int)fileLength) : IoUtils.toByteArray(in); + } finally { + in.close(); + } + } + + /** + * Opens a {@link FileInputStream} for the specified file, providing better error messages than simply calling + * new FileInputStream(file). + *

+ * At the end of the method either the stream will be successfully opened, or an exception will have been thrown. + *

+ * An exception is thrown if the file does not exist. An exception is thrown if the file object exists but is a + * directory. An exception is thrown if the file exists but cannot be read. + * + * @param file + * @return + * @throws IOException + */ + public static FileInputStream openInputStream(final File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canRead() == false) { + throw new IOException("File '" + file + "' cannot be read"); + } + } else { + throw new FileNotFoundException("File '" + file + "' does not exist"); + } + return new FileInputStream(file); + } + + /** + * Write inputStream to file + * + * @param file file + * @param inputStream inputStream + */ + public static void writeToFile(File file, InputStream inputStream) { + writeToFile(file, inputStream, true); + } + + /** + * Write inputStream to file + * + * @param file file + * @param inputStream inputStream + * @param closeInputStream closeInputStream + */ + public static void writeToFile(File file, InputStream inputStream, boolean closeInputStream) { + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(file); + int bytesRead; + byte[] buffer = new byte[WRITE_BUFF_SIZE]; + while ((bytesRead = inputStream.read(buffer, 0, WRITE_BUFF_SIZE)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (Exception e) { + throw new ExcelAnalysisException("Can not create temporary file!", e); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + throw new ExcelAnalysisException("Can not close 'outputStream'!", e); + } + } + if (inputStream != null && closeInputStream) { + try { + inputStream.close(); + } catch (IOException e) { + throw new ExcelAnalysisException("Can not close 'inputStream'", e); + } + } + } + } + + public static void createPoiFilesDirectory() { + TempFile.setTempFileCreationStrategy(new EasyExcelTempFileCreationStrategy()); + } + + public static File createCacheTmpFile() { + return createDirectory(new File(cachePath + UUID.randomUUID().toString())); + } + + public static File createTmpFile(String fileName) { + File directory = createDirectory(new File(tempFilePrefix)); + return new File(directory, fileName); + } + + /** + * @param directory + */ + public static File createDirectory(File directory) { + if (!directory.exists() && !directory.mkdirs()) { + throw new ExcelCommonException("Cannot create directory:" + directory.getAbsolutePath()); + } + return directory; + } + + /** + * delete file + * + * @param file + */ + public static void delete(File file) { + if (file.isFile()) { + file.delete(); + return; + } + if (file.isDirectory()) { + File[] childFiles = file.listFiles(); + if (childFiles == null || childFiles.length == 0) { + file.delete(); + return; + } + for (int i = 0; i < childFiles.length; i++) { + delete(childFiles[i]); + } + file.delete(); + } + } + + public static String getTempFilePrefix() { + return tempFilePrefix; + } + + public static void setTempFilePrefix(String tempFilePrefix) { + FileUtils.tempFilePrefix = tempFilePrefix; + } + + public static String getPoiFilesPath() { + return poiFilesPath; + } + + public static void setPoiFilesPath(String poiFilesPath) { + FileUtils.poiFilesPath = poiFilesPath; + } + + public static String getCachePath() { + return cachePath; + } + + public static void setCachePath(String cachePath) { + FileUtils.cachePath = cachePath; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IntUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IntUtils.java new file mode 100644 index 0000000..c4e1004 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IntUtils.java @@ -0,0 +1,36 @@ +package ai.chat2db.excel.util; + +/** + * Int utils + * + * @author Jiaju Zhuang + **/ +public class IntUtils { + private IntUtils() {} + + + /** + * The largest power of two that can be represented as an {@code int}. + * + * @since 10.0 + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the {@code int} type, + * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too + * small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IoUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IoUtils.java new file mode 100644 index 0000000..02b0de3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/IoUtils.java @@ -0,0 +1,87 @@ +package ai.chat2db.excel.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * IO Utils + * + * @author Jiaju Zhuang + */ +public class IoUtils { + public static final int EOF = -1; + /** + * The default buffer size ({@value}) to use for + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + private IoUtils() {} + + /** + * Gets the contents of an InputStream as a byte[]. + * + * @param input + * @return + * @throws IOException + */ + public static byte[] toByteArray(final InputStream input) throws IOException { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + copy(input, output); + return output.toByteArray(); + } finally { + output.toByteArray(); + } + } + + /** + * Gets the contents of an InputStream as a byte[]. + * + * @param input + * @param size + * @return + * @throws IOException + */ + public static byte[] toByteArray(final InputStream input, final int size) throws IOException { + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + } + if (size == 0) { + return new byte[0]; + } + final byte[] data = new byte[size]; + int offset = 0; + int read; + while (offset < size && (read = input.read(data, offset, size - offset)) != EOF) { + offset += read; + } + if (offset != size) { + throw new IOException("Unexpected read size. current: " + offset + ", expected: " + size); + } + return data; + } + + /** + * Copies bytes + * + * @param input + * @param output + * @return + * @throws IOException + */ + public static int copy(final InputStream input, final OutputStream output) throws IOException { + long count = 0; + int n; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int)count; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ListUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ListUtils.java new file mode 100644 index 0000000..ef3db00 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/ListUtils.java @@ -0,0 +1,147 @@ +package ai.chat2db.excel.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import lombok.NonNull; +import org.apache.commons.compress.utils.Iterators; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class ListUtils { + private ListUtils() {} + + /** + * Creates a mutable, empty {@code ArrayList} instance (for Java 6 and earlier). + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code ArrayList} {@linkplain ArrayList#ArrayList() constructor} + * directly, taking advantage of the new "diamond" syntax. + */ + public static ArrayList newArrayList() { + return new ArrayList<>(); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements. + * + */ + public static ArrayList newArrayList(E... elements) { + checkNotNull(elements); + // Avoid integer overflow when a large array is passed in + int capacity = computeArrayListCapacity(elements.length); + ArrayList list = new ArrayList<>(capacity); + Collections.addAll(list, elements); + return list; + } + + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements; a very thin + * shortcut for creating an empty list and then calling {@link Iterators#addAll}. + * + */ + public static ArrayList newArrayList(Iterator elements) { + ArrayList list = newArrayList(); + Iterators.addAll(list, elements); + return list; + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements; + * + * + *

Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't + * need this method. Use the {@code ArrayList} {@linkplain ArrayList#ArrayList(Collection) + * constructor} directly, taking advantage of the new "diamond" + * syntax. + */ + public static ArrayList newArrayList(Iterable elements) { + checkNotNull(elements); // for GWT + // Let ArrayList's sizing logic work, if possible + return (elements instanceof Collection) + ? new ArrayList<>((Collection)elements) + : newArrayList(elements.iterator()); + } + + /** + * Creates an {@code ArrayList} instance backed by an array with the specified initial size; + * simply delegates to {@link ArrayList#ArrayList(int)}. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use {@code new }{@link ArrayList#ArrayList(int) ArrayList}{@code <>(int)} + * directly, taking advantage of the new "diamond" syntax. + * (Unlike here, there is no risk of overload ambiguity, since the {@code ArrayList} constructors + * very wisely did not accept varargs.) + * + * @param initialArraySize the exact size of the initial backing array for the returned array list + * ({@code ArrayList} documentation calls this value the "capacity") + * @return a new, empty {@code ArrayList} which is guaranteed not to resize itself unless its size + * reaches {@code initialArraySize + 1} + * @throws IllegalArgumentException if {@code initialArraySize} is negative + */ + public static ArrayList newArrayListWithCapacity(int initialArraySize) { + checkNonnegative(initialArraySize, "initialArraySize"); + return new ArrayList<>(initialArraySize); + } + + /** + * Creates an {@code ArrayList} instance to hold {@code estimatedSize} elements, plus an + * unspecified amount of padding; you almost certainly mean to call {@link + * #newArrayListWithCapacity} (see that method for further advice on usage). + * + *

Note: This method will soon be deprecated. Even in the rare case that you do want + * some amount of padding, it's best if you choose your desired amount explicitly. + * + * @param estimatedSize an estimate of the eventual {@link List#size()} of the new list + * @return a new, empty {@code ArrayList}, sized appropriately to hold the estimated number of + * elements + * @throws IllegalArgumentException if {@code estimatedSize} is negative + */ + public static ArrayList newArrayListWithExpectedSize(int estimatedSize) { + return new ArrayList<>(computeArrayListCapacity(estimatedSize)); + } + + static int computeArrayListCapacity(int arraySize) { + checkNonnegative(arraySize, "arraySize"); + return IntUtils.saturatedCast(5L + arraySize + (arraySize / 10)); + } + + static int checkNonnegative(int value, String name) { + if (value < 0) { + throw new IllegalArgumentException(name + " cannot be negative but was: " + value); + } + return value; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MapUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MapUtils.java new file mode 100644 index 0000000..c04e6bc --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MapUtils.java @@ -0,0 +1,123 @@ +package ai.chat2db.excel.util; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.TreeMap; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class MapUtils { + + private MapUtils() {} + + + /** + * Creates a mutable, empty {@code HashMap} instance. + * + *

Note: if mutability is not required, use ImmutableMap.of() instead. + * + *

Note: if {@code K} is an {@code enum} type, use newEnumMap instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code HashMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @return a new, empty {@code HashMap} + */ + public static HashMap newHashMap() { + return new HashMap<>(16); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the natural ordering of its + * elements. + * + *

Note: if mutability is not required, use ImmutableSortedMap.of() instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap() { + return new TreeMap<>(); + } + + /** + * Creates a {@code HashMap} instance, with a high enough "initial capacity" that it should + * hold {@code expectedSize} elements without growth. This behavior cannot be broadly guaranteed, + * but it is observed to be true for OpenJDK 1.7. It also can't be guaranteed that the method + * isn't inadvertently oversizing the returned map. + * + * @param expectedSize the number of entries you expect to add to the returned map + * @return a new, empty {@code HashMap} with enough capacity to hold {@code expectedSize} entries + * without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize(int expectedSize) { + return new HashMap<>(capacity(expectedSize)); + } + + /** + * Creates a mutable, empty, insertion-ordered {@code LinkedHashMap} instance. + * + *

Note: if mutability is not required, use ImmutableMap.of() instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code LinkedHashMap} constructor directly, taking advantage of + * the new "diamond" syntax. + * + * @return a new, empty {@code LinkedHashMap} + */ + public static LinkedHashMap newLinkedHashMap() { + return new LinkedHashMap<>(); + } + + /** + * Creates a {@code LinkedHashMap} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without growth. This behavior cannot be + * broadly guaranteed, but it is observed to be true for OpenJDK 1.7. It also can't be guaranteed + * that the method isn't inadvertently oversizing the returned map. + * + * @param expectedSize the number of entries you expect to add to the returned map + * @return a new, empty {@code LinkedHashMap} with enough capacity to hold {@code expectedSize} + * entries without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + * @since 19.0 + */ + public static LinkedHashMap newLinkedHashMapWithExpectedSize(int expectedSize) { + return new LinkedHashMap<>(capacity(expectedSize)); + } + + /** + * Returns a capacity that is sufficient to keep the map from being resized as long as it grows no + * larger than expectedSize and the load factor is ≥ its default (0.75). + */ + static int capacity(int expectedSize) { + if (expectedSize < 3) { + return expectedSize + 1; + } + if (expectedSize < IntUtils.MAX_POWER_OF_TWO) { + // This is the calculation used in JDK8 to resize when a putAll + // happens; it seems to be the most conservative calculation we + // can make. 0.75 is the default load factor. + return (int)((float)expectedSize / 0.75F + 1.0F); + } + return Integer.MAX_VALUE; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MemberUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MemberUtils.java new file mode 100644 index 0000000..b9c36ec --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/MemberUtils.java @@ -0,0 +1,65 @@ +package ai.chat2db.excel.util; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class MemberUtils { + + private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; + + + /** + * XXX Default access superclass workaround. + * + * When a {@code public} class has a default access superclass with {@code public} members, + * these members are accessible. Calling them from compiled code works fine. + * Unfortunately, on some JVMs, using reflection to invoke these members + * seems to (wrongly) prevent access even when the modifier is {@code public}. + * Calling {@code setAccessible(true)} solves the problem but will only work from + * sufficiently privileged code. Better workarounds would be gratefully + * accepted. + * @param o the AccessibleObject to set as accessible + * @return a boolean indicating whether the accessibility of the object was set to true. + */ + static boolean setAccessibleWorkaround(final AccessibleObject o) { + if (o == null || o.isAccessible()) { + return false; + } + final Member m = (Member) o; + if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + o.setAccessible(true); + return true; + } catch (final SecurityException e) { // NOPMD + // ignore in favor of subsequent IllegalAccessException + } + } + return false; + } + + /** + * Returns whether a given set of modifiers implies package access. + * @param modifiers to test + * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected + */ + static boolean isPackageAccess(final int modifiers) { + return (modifiers & ACCESS_TEST) == 0; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberDataFormatterUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberDataFormatterUtils.java new file mode 100644 index 0000000..f580c34 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberDataFormatterUtils.java @@ -0,0 +1,63 @@ +package ai.chat2db.excel.util; + +import java.math.BigDecimal; +import java.util.Locale; + +import ai.chat2db.excel.metadata.GlobalConfiguration; +import ai.chat2db.excel.metadata.format.DataFormatter; + +/** + * Convert number data, including date. + * + * @author Jiaju Zhuang + **/ +public class NumberDataFormatterUtils { + + /** + * Cache DataFormatter. + */ + private static final ThreadLocal DATA_FORMATTER_THREAD_LOCAL = new ThreadLocal(); + + /** + * Format number data. + * + * @param data + * @param dataFormat Not null. + * @param dataFormatString + * @param globalConfiguration + * @return + */ + public static String format(BigDecimal data, Short dataFormat, String dataFormatString, + GlobalConfiguration globalConfiguration) { + if (globalConfiguration == null) { + return format(data, dataFormat, dataFormatString, null, null, null); + } + return format(data, dataFormat, dataFormatString, globalConfiguration.getUse1904windowing(), + globalConfiguration.getLocale(), globalConfiguration.getUseScientificFormat()); + } + + /** + * Format number data. + * + * @param data + * @param dataFormat Not null. + * @param dataFormatString + * @param use1904windowing + * @param locale + * @param useScientificFormat + * @return + */ + public static String format(BigDecimal data, Short dataFormat, String dataFormatString, Boolean use1904windowing, + Locale locale, Boolean useScientificFormat) { + DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get(); + if (dataFormatter == null) { + dataFormatter = new DataFormatter(use1904windowing, locale, useScientificFormat); + DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter); + } + return dataFormatter.format(data, dataFormat, dataFormatString); + } + + public static void removeThreadLocalCache() { + DATA_FORMATTER_THREAD_LOCAL.remove(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberUtils.java new file mode 100644 index 0000000..5457f49 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/NumberUtils.java @@ -0,0 +1,190 @@ +package ai.chat2db.excel.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.ParseException; + +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; + +/** + * Number utils + * + * @author Jiaju Zhuang + */ +public class NumberUtils { + private NumberUtils() {} + + /** + * format + * + * @param num + * @param contentProperty + * @return + */ + public static String format(Number num, ExcelContentProperty contentProperty) { + if (contentProperty == null || contentProperty.getNumberFormatProperty() == null + || StringUtils.isEmpty(contentProperty.getNumberFormatProperty().getFormat())) { + if (num instanceof BigDecimal) { + return ((BigDecimal)num).toPlainString(); + } else { + return num.toString(); + } + } + String format = contentProperty.getNumberFormatProperty().getFormat(); + RoundingMode roundingMode = contentProperty.getNumberFormatProperty().getRoundingMode(); + DecimalFormat decimalFormat = new DecimalFormat(format); + decimalFormat.setRoundingMode(roundingMode); + return decimalFormat.format(num); + } + + /** + * format + * + * @param num + * @param contentProperty + * @return + */ + public static WriteCellData formatToCellDataString(Number num, ExcelContentProperty contentProperty) { + return new WriteCellData<>(format(num, contentProperty)); + } + + /** + * format + * + * @param num + * @param contentProperty + * @return + */ + public static WriteCellData formatToCellData(Number num, ExcelContentProperty contentProperty) { + WriteCellData cellData = new WriteCellData<>(new BigDecimal(num.toString())); + if (contentProperty != null && contentProperty.getNumberFormatProperty() != null + && StringUtils.isNotBlank(contentProperty.getNumberFormatProperty().getFormat())) { + WorkBookUtil.fillDataFormat(cellData, contentProperty.getNumberFormatProperty().getFormat(), null); + } + return cellData; + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static Short parseShort(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).shortValue(); + } + return parse(string, contentProperty).shortValue(); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static Long parseLong(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).longValue(); + } + return parse(string, contentProperty).longValue(); + } + + /** + * parse Integer from string + * + * @param string An integer read in string format + * @param contentProperty Properties of the content read in + * @return An integer converted from a string + */ + public static Integer parseInteger(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).intValue(); + } + return parse(string, contentProperty).intValue(); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static Float parseFloat(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).floatValue(); + } + return parse(string, contentProperty).floatValue(); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static BigDecimal parseBigDecimal(String string, ExcelContentProperty contentProperty) + throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string); + } + return new BigDecimal(parse(string, contentProperty).toString()); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static Byte parseByte(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).byteValue(); + } + return parse(string, contentProperty).byteValue(); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + */ + public static Double parseDouble(String string, ExcelContentProperty contentProperty) throws ParseException { + if (!hasFormat(contentProperty)) { + return new BigDecimal(string).doubleValue(); + } + return parse(string, contentProperty).doubleValue(); + } + + private static boolean hasFormat(ExcelContentProperty contentProperty) { + return contentProperty != null && contentProperty.getNumberFormatProperty() != null + && !StringUtils.isEmpty(contentProperty.getNumberFormatProperty().getFormat()); + } + + /** + * parse + * + * @param string + * @param contentProperty + * @return + * @throws ParseException + */ + private static Number parse(String string, ExcelContentProperty contentProperty) throws ParseException { + String format = contentProperty.getNumberFormatProperty().getFormat(); + RoundingMode roundingMode = contentProperty.getNumberFormatProperty().getRoundingMode(); + DecimalFormat decimalFormat = new DecimalFormat(format); + decimalFormat.setRoundingMode(roundingMode); + decimalFormat.setParseBigDecimal(true); + return decimalFormat.parse(string); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PoiUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PoiUtils.java new file mode 100644 index 0000000..ecaba6f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PoiUtils.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.util; + +import org.apache.poi.hssf.record.RowRecord; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.xssf.usermodel.XSSFRow; + +import java.lang.reflect.Field; + +/** + * utils + * + * @author Jiaju Zhuang + */ +public class PoiUtils { + + /** + * Whether to customize the height + */ + public static final BitField CUSTOM_HEIGHT = BitFieldFactory.getInstance(0x640); + + private static final Field ROW_RECORD_FIELD = FieldUtils.getField(HSSFRow.class, "row", true); + + /** + * Whether to customize the height + * + * @param row row + * @return + */ + public static boolean customHeight(Row row) { + if (row instanceof XSSFRow) { + XSSFRow xssfRow = (XSSFRow)row; + return xssfRow.getCTRow().getCustomHeight(); + } + if (row instanceof HSSFRow) { + HSSFRow hssfRow = (HSSFRow)row; + try { + RowRecord record = (RowRecord)ROW_RECORD_FIELD.get(hssfRow); + return CUSTOM_HEIGHT.getValue(record.getOptionFlags()) == 1; + } catch (IllegalAccessException ignore) { + } + } + return false; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PositionUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PositionUtils.java new file mode 100644 index 0000000..b5afec1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/PositionUtils.java @@ -0,0 +1,64 @@ +package ai.chat2db.excel.util; + +import java.util.regex.Pattern; + +/** + * @author jipengfei + */ +public class PositionUtils { + + private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Z]+)?" + "(\\$?[0-9]+)?", + Pattern.CASE_INSENSITIVE); + private static final char SHEET_NAME_DELIMITER = '!'; + private static final char REDUNDANT_CHARACTERS = '$'; + + private PositionUtils() {} + + public static int getRowByRowTagt(String rowTagt, Integer before) { + int row; + if (rowTagt != null) { + row = Integer.parseInt(rowTagt) - 1; + return row; + } else { + if (before == null) { + before = -1; + } + return before + 1; + } + } + + public static int getRow(String currentCellIndex) { + if (currentCellIndex == null) { + return -1; + } + int firstNumber = currentCellIndex.length() - 1; + for (; firstNumber >= 0; firstNumber--) { + char c = currentCellIndex.charAt(firstNumber); + if (c < '0' || c > '9') { + break; + } + } + return Integer.parseUnsignedInt(currentCellIndex.substring(firstNumber + 1)) - 1; + } + + public static int getCol(String currentCellIndex, Integer before) { + if (currentCellIndex == null) { + if (before == null) { + before = -1; + } + return before + 1; + } + int firstNumber = currentCellIndex.charAt(0) == REDUNDANT_CHARACTERS ? 1 : 0; + int col = 0; + for (; firstNumber < currentCellIndex.length(); firstNumber++) { + char c = currentCellIndex.charAt(firstNumber); + boolean isNotLetter = c == REDUNDANT_CHARACTERS || (c >= '0' && c <= '9'); + if (isNotLetter) { + break; + } + col = col * 26 + Character.toUpperCase(c) - 'A' + 1; + } + return col - 1; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/SheetUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/SheetUtils.java new file mode 100644 index 0000000..be7c935 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/SheetUtils.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.ReadWorkbookHolder; + +/** + * Sheet utils + * + * @author Jiaju Zhuang + */ +public class SheetUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(SheetUtils.class); + + private SheetUtils() {} + + /** + * Match the parameters to the actual sheet + * + * @param readSheet actual sheet + * @param analysisContext + * @return + */ + public static ReadSheet match(ReadSheet readSheet, AnalysisContext analysisContext) { + ReadWorkbookHolder readWorkbookHolder = analysisContext.readWorkbookHolder(); + if (readWorkbookHolder.getReadAll()) { + return readSheet; + } + for (ReadSheet parameterReadSheet : readWorkbookHolder.getParameterSheetDataList()) { + if (parameterReadSheet == null) { + continue; + } + if (parameterReadSheet.getSheetNo() == null && parameterReadSheet.getSheetName() == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("The first is read by default."); + } + parameterReadSheet.setSheetNo(0); + } + boolean match = (parameterReadSheet.getSheetNo() != null + && parameterReadSheet.getSheetNo().equals(readSheet.getSheetNo())); + if (!match) { + String parameterSheetName = parameterReadSheet.getSheetName(); + if (!StringUtils.isEmpty(parameterSheetName)) { + boolean autoTrim = (parameterReadSheet.getAutoTrim() != null && parameterReadSheet.getAutoTrim()) + || (parameterReadSheet.getAutoTrim() == null + && analysisContext.readWorkbookHolder().getGlobalConfiguration().getAutoTrim()); + String sheetName = readSheet.getSheetName(); + if (autoTrim) { + parameterSheetName = parameterSheetName.trim(); + sheetName = sheetName.trim(); + } + match = parameterSheetName.equals(sheetName); + } + } + if (match) { + readSheet.copyBasicParameter(parameterReadSheet); + return readSheet; + } + } + return null; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StringUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StringUtils.java new file mode 100644 index 0000000..f93af4e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StringUtils.java @@ -0,0 +1,242 @@ +package ai.chat2db.excel.util; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class StringUtils { + private StringUtils() {} + + /** + * A String for a space character. + */ + public static final String SPACE = " "; + + /** + * The empty String {@code ""}. + */ + public static final String EMPTY = ""; + + /** + *

Checks if a CharSequence is empty ("") or null.

+ * + *
+     * StringUtils.isEmpty(null)      = true
+     * StringUtils.isEmpty("")        = true
+     * StringUtils.isEmpty(" ")       = false
+     * StringUtils.isEmpty("bob")     = false
+     * StringUtils.isEmpty("  bob  ") = false
+     * 
+ * + *

NOTE: This method changed in Lang version 2.0. + * It no longer trims the CharSequence. + * That functionality is available in isBlank().

+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + *

Checks if a CharSequence is empty (""), null or whitespace only.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isBlank(null)      = true
+     * StringUtils.isBlank("")        = true
+     * StringUtils.isBlank(" ")       = true
+     * StringUtils.isBlank("bob")     = false
+     * StringUtils.isBlank("  bob  ") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace only + */ + public static boolean isBlank(final CharSequence cs) { + int strLen; + if (cs == null || (strLen = cs.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + return true; + } + + /** + *

Checks if a CharSequence is not empty (""), not null and not whitespace only.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isNotBlank(null)      = false
+     * StringUtils.isNotBlank("")        = false
+     * StringUtils.isNotBlank(" ")       = false
+     * StringUtils.isNotBlank("bob")     = true
+     * StringUtils.isNotBlank("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace only + * @since 2.0 + * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) + */ + public static boolean isNotBlank(final CharSequence cs) { + return !isBlank(cs); + } + + /** + *

Compares two CharSequences, returning {@code true} if they represent + * equal sequences of characters.

+ * + *

{@code null}s are handled without exceptions. Two {@code null} + * references are considered to be equal. The comparison is case sensitive.

+ * + *
+     * StringUtils.equals(null, null)   = true
+     * StringUtils.equals(null, "abc")  = false
+     * StringUtils.equals("abc", null)  = false
+     * StringUtils.equals("abc", "abc") = true
+     * StringUtils.equals("abc", "ABC") = false
+     * 
+ * + * @param cs1 the first CharSequence, may be {@code null} + * @param cs2 the second CharSequence, may be {@code null} + * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null} + * @see Object#equals(Object) + * @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence) + */ + public static boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1.length() != cs2.length()) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String) { + return cs1.equals(cs2); + } + return regionMatches(cs1, false, 0, cs2, 0, cs1.length()); + } + + /** + * Green implementation of regionMatches. + * + * @param cs the {@code CharSequence} to be processed + * @param ignoreCase whether or not to be case insensitive + * @param thisStart the index to start on the {@code cs} CharSequence + * @param substring the {@code CharSequence} to be looked for + * @param start the index to start on the {@code substring} CharSequence + * @param length character length of the region + * @return whether the region matched + */ + public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String)cs).regionMatches(ignoreCase, thisStart, (String)substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + // Extract these first so we detect NPEs the same as the java.lang.String version + final int srcLen = cs.length() - thisStart; + final int otherLen = substring.length() - start; + + // Check for invalid parameters + if (thisStart < 0 || start < 0 || length < 0) { + return false; + } + + // Check that the regions are long enough + if (srcLen < length || otherLen < length) { + return false; + } + + while (tmpLen-- > 0) { + final char c1 = cs.charAt(index1++); + final char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) + && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } + + /** + *

Checks if the CharSequence contains only Unicode digits. + * A decimal point is not a Unicode digit and returns false.

+ * + *

{@code null} will return {@code false}. + * An empty CharSequence (length()=0) will return {@code false}.

+ * + *

Note that the method does not allow for a leading sign, either positive or negative. + * Also, if a String passes the numeric test, it may still generate a NumberFormatException + * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range + * for int or long respectively.

+ * + *
+     * StringUtils.isNumeric(null)   = false
+     * StringUtils.isNumeric("")     = false
+     * StringUtils.isNumeric("  ")   = false
+     * StringUtils.isNumeric("123")  = true
+     * StringUtils.isNumeric("\u0967\u0968\u0969")  = true
+     * StringUtils.isNumeric("12 3") = false
+     * StringUtils.isNumeric("ab2c") = false
+     * StringUtils.isNumeric("12-3") = false
+     * StringUtils.isNumeric("12.3") = false
+     * StringUtils.isNumeric("-123") = false
+     * StringUtils.isNumeric("+123") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits, and is non-null + * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence) + * @since 3.0 Changed "" to return false and not true + */ + public static boolean isNumeric(final CharSequence cs) { + if (isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (!Character.isDigit(cs.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StyleUtil.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StyleUtil.java new file mode 100644 index 0000000..bded148 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/StyleUtil.java @@ -0,0 +1,272 @@ +package ai.chat2db.excel.util; + +import java.util.Optional; + +import ai.chat2db.excel.constant.BuiltinFormats; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.data.HyperlinkData; +import ai.chat2db.excel.metadata.data.RichTextStringData; +import ai.chat2db.excel.metadata.data.RichTextStringData.IntervalFont; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.hssf.usermodel.HSSFFont; +import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.Units; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; + +/** + * @author jipengfei + */ +@Slf4j +public class StyleUtil { + + private StyleUtil() {} + + /** + * Build cell style + * + * @param workbook + * @param originCellStyle + * @param writeCellStyle + * @return + */ + public static CellStyle buildCellStyle(Workbook workbook, CellStyle originCellStyle, + WriteCellStyle writeCellStyle) { + CellStyle cellStyle = workbook.createCellStyle(); + if (originCellStyle != null) { + cellStyle.cloneStyleFrom(originCellStyle); + } + if (writeCellStyle == null) { + return cellStyle; + } + buildCellStyle(cellStyle, writeCellStyle); + return cellStyle; + } + + private static void buildCellStyle(CellStyle cellStyle, WriteCellStyle writeCellStyle) { + if (writeCellStyle.getHidden() != null) { + cellStyle.setHidden(writeCellStyle.getHidden()); + } + if (writeCellStyle.getLocked() != null) { + cellStyle.setLocked(writeCellStyle.getLocked()); + } + if (writeCellStyle.getQuotePrefix() != null) { + cellStyle.setQuotePrefixed(writeCellStyle.getQuotePrefix()); + } + if (writeCellStyle.getHorizontalAlignment() != null) { + cellStyle.setAlignment(writeCellStyle.getHorizontalAlignment()); + } + if (writeCellStyle.getWrapped() != null) { + cellStyle.setWrapText(writeCellStyle.getWrapped()); + } + if (writeCellStyle.getVerticalAlignment() != null) { + cellStyle.setVerticalAlignment(writeCellStyle.getVerticalAlignment()); + } + if (writeCellStyle.getRotation() != null) { + cellStyle.setRotation(writeCellStyle.getRotation()); + } + if (writeCellStyle.getIndent() != null) { + cellStyle.setIndention(writeCellStyle.getIndent()); + } + if (writeCellStyle.getBorderLeft() != null) { + cellStyle.setBorderLeft(writeCellStyle.getBorderLeft()); + } + if (writeCellStyle.getBorderRight() != null) { + cellStyle.setBorderRight(writeCellStyle.getBorderRight()); + } + if (writeCellStyle.getBorderTop() != null) { + cellStyle.setBorderTop(writeCellStyle.getBorderTop()); + } + if (writeCellStyle.getBorderBottom() != null) { + cellStyle.setBorderBottom(writeCellStyle.getBorderBottom()); + } + if (writeCellStyle.getLeftBorderColor() != null) { + cellStyle.setLeftBorderColor(writeCellStyle.getLeftBorderColor()); + } + if (writeCellStyle.getRightBorderColor() != null) { + cellStyle.setRightBorderColor(writeCellStyle.getRightBorderColor()); + } + if (writeCellStyle.getTopBorderColor() != null) { + cellStyle.setTopBorderColor(writeCellStyle.getTopBorderColor()); + } + if (writeCellStyle.getBottomBorderColor() != null) { + cellStyle.setBottomBorderColor(writeCellStyle.getBottomBorderColor()); + } + if (writeCellStyle.getFillPatternType() != null) { + cellStyle.setFillPattern(writeCellStyle.getFillPatternType()); + } + if (writeCellStyle.getFillBackgroundColor() != null) { + cellStyle.setFillBackgroundColor(writeCellStyle.getFillBackgroundColor()); + } + if (writeCellStyle.getFillForegroundColor() != null) { + cellStyle.setFillForegroundColor(writeCellStyle.getFillForegroundColor()); + } + if (writeCellStyle.getShrinkToFit() != null) { + cellStyle.setShrinkToFit(writeCellStyle.getShrinkToFit()); + } + } + + public static short buildDataFormat(Workbook workbook, DataFormatData dataFormatData) { + if (dataFormatData == null) { + return BuiltinFormats.GENERAL; + } + if (dataFormatData.getIndex() != null && dataFormatData.getIndex() >= 0) { + return dataFormatData.getIndex(); + } + if (StringUtils.isNotBlank(dataFormatData.getFormat())) { + if (log.isDebugEnabled()) { + log.info("create new data fromat:{}", dataFormatData); + } + DataFormat dataFormatCreate = workbook.createDataFormat(); + return dataFormatCreate.getFormat(dataFormatData.getFormat()); + } + return BuiltinFormats.GENERAL; + } + + public static Font buildFont(Workbook workbook, Font originFont, WriteFont writeFont) { + if (log.isDebugEnabled()) { + log.info("create new font:{},{}", writeFont, originFont); + } + if (writeFont == null && originFont == null) { + return null; + } + Font font = createFont(workbook, originFont, writeFont); + if (writeFont == null || font == null) { + return font; + } + if (writeFont.getFontName() != null) { + font.setFontName(writeFont.getFontName()); + } + if (writeFont.getFontHeightInPoints() != null) { + font.setFontHeightInPoints(writeFont.getFontHeightInPoints()); + } + if (writeFont.getItalic() != null) { + font.setItalic(writeFont.getItalic()); + } + if (writeFont.getStrikeout() != null) { + font.setStrikeout(writeFont.getStrikeout()); + } + if (writeFont.getColor() != null) { + font.setColor(writeFont.getColor()); + } + if (writeFont.getTypeOffset() != null) { + font.setTypeOffset(writeFont.getTypeOffset()); + } + if (writeFont.getUnderline() != null) { + font.setUnderline(writeFont.getUnderline()); + } + if (writeFont.getCharset() != null) { + font.setCharSet(writeFont.getCharset()); + } + if (writeFont.getBold() != null) { + font.setBold(writeFont.getBold()); + } + return font; + } + + private static Font createFont(Workbook workbook, Font originFont, WriteFont writeFont) { + Font font = workbook.createFont(); + if (originFont == null) { + return font; + } + if (originFont instanceof XSSFFont) { + XSSFFont xssfFont = (XSSFFont)font; + XSSFFont xssfOriginFont = ((XSSFFont)originFont); + xssfFont.setFontName(xssfOriginFont.getFontName()); + xssfFont.setFontHeightInPoints(xssfOriginFont.getFontHeightInPoints()); + xssfFont.setItalic(xssfOriginFont.getItalic()); + xssfFont.setStrikeout(xssfOriginFont.getStrikeout()); + // Colors cannot be overwritten + if (writeFont == null || writeFont.getColor() == null) { + xssfFont.setColor(Optional.of(xssfOriginFont) + .map(XSSFFont::getXSSFColor) + .map(XSSFColor::getRGB) + .map(rgb -> new XSSFColor(rgb, null)) + .orElse(null)); + } + xssfFont.setTypeOffset(xssfOriginFont.getTypeOffset()); + xssfFont.setUnderline(xssfOriginFont.getUnderline()); + xssfFont.setCharSet(xssfOriginFont.getCharSet()); + xssfFont.setBold(xssfOriginFont.getBold()); + return xssfFont; + } else if (originFont instanceof HSSFFont) { + HSSFFont hssfFont = (HSSFFont)font; + HSSFFont hssfOriginFont = (HSSFFont)originFont; + hssfFont.setFontName(hssfOriginFont.getFontName()); + hssfFont.setFontHeightInPoints(hssfOriginFont.getFontHeightInPoints()); + hssfFont.setItalic(hssfOriginFont.getItalic()); + hssfFont.setStrikeout(hssfOriginFont.getStrikeout()); + hssfFont.setColor(hssfOriginFont.getColor()); + hssfFont.setTypeOffset(hssfOriginFont.getTypeOffset()); + hssfFont.setUnderline(hssfOriginFont.getUnderline()); + hssfFont.setCharSet(hssfOriginFont.getCharSet()); + hssfFont.setBold(hssfOriginFont.getBold()); + return hssfFont; + } + return font; + } + + public static RichTextString buildRichTextString(WriteWorkbookHolder writeWorkbookHolder, + RichTextStringData richTextStringData) { + if (richTextStringData == null) { + return null; + } + RichTextString richTextString; + if (writeWorkbookHolder.getExcelType() == ExcelTypeEnum.XLSX) { + richTextString = new XSSFRichTextString(richTextStringData.getTextString()); + } else { + richTextString = new HSSFRichTextString(richTextStringData.getTextString()); + } + if (richTextStringData.getWriteFont() != null) { + richTextString.applyFont(writeWorkbookHolder.createFont(richTextStringData.getWriteFont(), null, true)); + } + if (CollectionUtils.isNotEmpty(richTextStringData.getIntervalFontList())) { + for (IntervalFont intervalFont : richTextStringData.getIntervalFontList()) { + richTextString.applyFont(intervalFont.getStartIndex(), intervalFont.getEndIndex(), + writeWorkbookHolder.createFont(intervalFont.getWriteFont(), null, true)); + } + } + return richTextString; + } + + public static HyperlinkType getHyperlinkType(HyperlinkData.HyperlinkType hyperlinkType) { + if (hyperlinkType == null) { + return HyperlinkType.NONE; + } + return hyperlinkType.getValue(); + } + + public static int getCoordinate(Integer coordinate) { + if (coordinate == null) { + return 0; + } + return Units.toEMU(coordinate); + } + + public static int getCellCoordinate(Integer currentCoordinate, Integer absoluteCoordinate, + Integer relativeCoordinate) { + if (absoluteCoordinate != null && absoluteCoordinate > 0) { + return absoluteCoordinate; + } + if (relativeCoordinate != null) { + return currentCoordinate + relativeCoordinate; + } + return currentCoordinate; + } + +} + + diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/Validate.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/Validate.java new file mode 100644 index 0000000..596c6a0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/Validate.java @@ -0,0 +1,162 @@ +package ai.chat2db.excel.util; + +import java.util.Objects; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Apache Software Foundation (ASF) + */ +public class Validate { + + private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; + private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null"; + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ * + *

For performance reasons, the long value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final long value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Long.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
+ * + *

For performance reasons, the double value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final double value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Double.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
+     * Validate.isTrue(myObject.isOk(), "The object is not okay");
+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + */ + public static void isTrue(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i > 0);
+     * Validate.isTrue(myObject.isOk());
+ * + *

The message of the exception is "The validated expression is + * false".

+ * + * @param expression the boolean expression to check + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression) { + if (!expression) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + *

The message of the exception is "The validated object is + * null".

+ * + * @param the object type + * @param object the object to check + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object, String, Object...) + */ + public static T notNull(final T object) { + return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + * @param the object type + * @param object the object to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object) + */ + public static T notNull(final T object, final String message, final Object... values) { + return Objects.requireNonNull(object, () -> String.format(message, values)); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WorkBookUtil.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WorkBookUtil.java new file mode 100644 index 0000000..da1c013 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WorkBookUtil.java @@ -0,0 +1,130 @@ +package ai.chat2db.excel.util; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import ai.chat2db.excel.metadata.csv.CsvWorkbook; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; + +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +/** + * @author jipengfei + */ +public class WorkBookUtil { + + private WorkBookUtil() {} + + public static void createWorkBook(WriteWorkbookHolder writeWorkbookHolder) throws IOException { + switch (writeWorkbookHolder.getExcelType()) { + case XLSX: + if (writeWorkbookHolder.getTempTemplateInputStream() != null) { + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTempTemplateInputStream()); + writeWorkbookHolder.setCachedWorkbook(xssfWorkbook); + if (writeWorkbookHolder.getInMemory()) { + writeWorkbookHolder.setWorkbook(xssfWorkbook); + } else { + writeWorkbookHolder.setWorkbook(new SXSSFWorkbook(xssfWorkbook)); + } + return; + } + Workbook workbook; + if (writeWorkbookHolder.getInMemory()) { + workbook = new XSSFWorkbook(); + } else { + workbook = new SXSSFWorkbook(); + } + writeWorkbookHolder.setCachedWorkbook(workbook); + writeWorkbookHolder.setWorkbook(workbook); + return; + case XLS: + HSSFWorkbook hssfWorkbook; + if (writeWorkbookHolder.getTempTemplateInputStream() != null) { + hssfWorkbook = new HSSFWorkbook( + new POIFSFileSystem(writeWorkbookHolder.getTempTemplateInputStream())); + } else { + hssfWorkbook = new HSSFWorkbook(); + } + writeWorkbookHolder.setCachedWorkbook(hssfWorkbook); + writeWorkbookHolder.setWorkbook(hssfWorkbook); + if (writeWorkbookHolder.getPassword() != null) { + Biff8EncryptionKey.setCurrentUserPassword(writeWorkbookHolder.getPassword()); + hssfWorkbook.writeProtectWorkbook(writeWorkbookHolder.getPassword(), StringUtils.EMPTY); + } + return; + case CSV: + CsvWorkbook csvWorkbook = new CsvWorkbook(new PrintWriter( + new OutputStreamWriter(writeWorkbookHolder.getOutputStream(), writeWorkbookHolder.getCharset())), + writeWorkbookHolder.getGlobalConfiguration().getLocale(), + writeWorkbookHolder.getGlobalConfiguration().getUse1904windowing(), + writeWorkbookHolder.getGlobalConfiguration().getUseScientificFormat(), + writeWorkbookHolder.getCharset(), + writeWorkbookHolder.getWithBom()); + writeWorkbookHolder.setCachedWorkbook(csvWorkbook); + writeWorkbookHolder.setWorkbook(csvWorkbook); + return; + default: + throw new UnsupportedOperationException("Wrong excel type."); + } + + } + + public static Sheet createSheet(Workbook workbook, String sheetName) { + return workbook.createSheet(sheetName); + } + + public static Row createRow(Sheet sheet, int rowNum) { + return sheet.createRow(rowNum); + } + + public static Cell createCell(Row row, int colNum) { + return row.createCell(colNum); + } + + public static Cell createCell(Row row, int colNum, CellStyle cellStyle) { + Cell cell = row.createCell(colNum); + cell.setCellStyle(cellStyle); + return cell; + } + + public static Cell createCell(Row row, int colNum, CellStyle cellStyle, String cellValue) { + Cell cell = createCell(row, colNum, cellStyle); + cell.setCellValue(cellValue); + return cell; + } + + public static Cell createCell(Row row, int colNum, String cellValue) { + Cell cell = row.createCell(colNum); + cell.setCellValue(cellValue); + return cell; + } + + public static void fillDataFormat(WriteCellData cellData, String format, String defaultFormat) { + if (cellData.getWriteCellStyle() == null) { + cellData.setWriteCellStyle(new WriteCellStyle()); + } + if (cellData.getWriteCellStyle().getDataFormatData() == null) { + cellData.getWriteCellStyle().setDataFormatData(new DataFormatData()); + } + if (cellData.getWriteCellStyle().getDataFormatData().getFormat() == null) { + if (format == null) { + cellData.getWriteCellStyle().getDataFormatData().setFormat(defaultFormat); + } else { + cellData.getWriteCellStyle().getDataFormatData().setFormat(format); + } + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WriteHandlerUtils.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WriteHandlerUtils.java new file mode 100644 index 0000000..d22a88d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/util/WriteHandlerUtils.java @@ -0,0 +1,182 @@ +package ai.chat2db.excel.util; + +import ai.chat2db.excel.write.handler.chain.CellHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.RowHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.SheetHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.WorkbookHandlerExecutionChain; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.SheetWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.write.metadata.holder.AbstractWriteHolder; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Row; + +/** + * Write handler utils + * + * @author Jiaju Zhuang + */ +@Slf4j +public class WriteHandlerUtils { + + private WriteHandlerUtils() {} + + public static WorkbookWriteHandlerContext createWorkbookWriteHandlerContext(WriteContext writeContext) { + WorkbookWriteHandlerContext context = new WorkbookWriteHandlerContext(writeContext, + writeContext.writeWorkbookHolder()); + writeContext.writeWorkbookHolder().setWorkbookWriteHandlerContext(context); + return context; + } + + public static void beforeWorkbookCreate(WorkbookWriteHandlerContext context) { + beforeWorkbookCreate(context, false); + } + + public static void beforeWorkbookCreate(WorkbookWriteHandlerContext context, boolean runOwn) { + WorkbookHandlerExecutionChain workbookHandlerExecutionChain = getWorkbookHandlerExecutionChain(context, runOwn); + if (workbookHandlerExecutionChain != null) { + workbookHandlerExecutionChain.beforeWorkbookCreate(context); + } + } + + public static void afterWorkbookCreate(WorkbookWriteHandlerContext context) { + afterWorkbookCreate(context, false); + } + + public static void afterWorkbookCreate(WorkbookWriteHandlerContext context, boolean runOwn) { + WorkbookHandlerExecutionChain workbookHandlerExecutionChain = getWorkbookHandlerExecutionChain(context, runOwn); + if (workbookHandlerExecutionChain != null) { + workbookHandlerExecutionChain.afterWorkbookCreate(context); + } + } + + private static WorkbookHandlerExecutionChain getWorkbookHandlerExecutionChain(WorkbookWriteHandlerContext context, + boolean runOwn) { + AbstractWriteHolder abstractWriteHolder = (AbstractWriteHolder)context.getWriteContext().currentWriteHolder(); + if (runOwn) { + return abstractWriteHolder.getOwnWorkbookHandlerExecutionChain(); + } else { + return abstractWriteHolder.getWorkbookHandlerExecutionChain(); + } + } + + public static void afterWorkbookDispose(WorkbookWriteHandlerContext context) { + WorkbookHandlerExecutionChain workbookHandlerExecutionChain = getWorkbookHandlerExecutionChain(context, false); + if (workbookHandlerExecutionChain != null) { + workbookHandlerExecutionChain.afterWorkbookDispose(context); + } + } + + public static SheetWriteHandlerContext createSheetWriteHandlerContext(WriteContext writeContext) { + return new SheetWriteHandlerContext(writeContext, writeContext.writeWorkbookHolder(), + writeContext.writeSheetHolder()); + } + + public static void beforeSheetCreate(SheetWriteHandlerContext context) { + beforeSheetCreate(context, false); + } + + public static void beforeSheetCreate(SheetWriteHandlerContext context, boolean runOwn) { + SheetHandlerExecutionChain sheetHandlerExecutionChain = getSheetHandlerExecutionChain(context, runOwn); + if (sheetHandlerExecutionChain != null) { + sheetHandlerExecutionChain.beforeSheetCreate(context); + } + } + + public static void afterSheetCreate(SheetWriteHandlerContext context) { + afterSheetCreate(context, false); + } + + public static void afterSheetCreate(SheetWriteHandlerContext context, boolean runOwn) { + SheetHandlerExecutionChain sheetHandlerExecutionChain = getSheetHandlerExecutionChain(context, runOwn); + if (sheetHandlerExecutionChain != null) { + sheetHandlerExecutionChain.afterSheetCreate(context); + } + } + + private static SheetHandlerExecutionChain getSheetHandlerExecutionChain(SheetWriteHandlerContext context, + boolean runOwn) { + AbstractWriteHolder abstractWriteHolder = (AbstractWriteHolder)context.getWriteContext().currentWriteHolder(); + if (runOwn) { + return abstractWriteHolder.getOwnSheetHandlerExecutionChain(); + } else { + return abstractWriteHolder.getSheetHandlerExecutionChain(); + } + } + + public static CellWriteHandlerContext createCellWriteHandlerContext(WriteContext writeContext, Row row, + Integer rowIndex, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead, + ExcelContentProperty excelContentProperty) { + return new CellWriteHandlerContext(writeContext, writeContext.writeWorkbookHolder(), + writeContext.writeSheetHolder(), writeContext.writeTableHolder(), row, rowIndex, null, columnIndex, + relativeRowIndex, head, null, null, isHead, excelContentProperty); + } + + public static void beforeCellCreate(CellWriteHandlerContext context) { + CellHandlerExecutionChain cellHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getCellHandlerExecutionChain(); + if (cellHandlerExecutionChain != null) { + cellHandlerExecutionChain.beforeCellCreate(context); + } + } + + public static void afterCellCreate(CellWriteHandlerContext context) { + CellHandlerExecutionChain cellHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getCellHandlerExecutionChain(); + if (cellHandlerExecutionChain != null) { + cellHandlerExecutionChain.afterCellCreate(context); + } + } + + public static void afterCellDataConverted(CellWriteHandlerContext context) { + CellHandlerExecutionChain cellHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getCellHandlerExecutionChain(); + if (cellHandlerExecutionChain != null) { + cellHandlerExecutionChain.afterCellDataConverted(context); + } + } + + public static void afterCellDispose(CellWriteHandlerContext context) { + CellHandlerExecutionChain cellHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getCellHandlerExecutionChain(); + if (cellHandlerExecutionChain != null) { + cellHandlerExecutionChain.afterCellDispose(context); + } + } + + public static RowWriteHandlerContext createRowWriteHandlerContext(WriteContext writeContext, Integer rowIndex, + Integer relativeRowIndex, Boolean isHead) { + return new RowWriteHandlerContext(writeContext, writeContext.writeWorkbookHolder(), + writeContext.writeSheetHolder(), writeContext.writeTableHolder(), rowIndex, null, relativeRowIndex, isHead); + } + + public static void beforeRowCreate(RowWriteHandlerContext context) { + RowHandlerExecutionChain rowHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getRowHandlerExecutionChain(); + if (rowHandlerExecutionChain != null) { + rowHandlerExecutionChain.beforeRowCreate(context); + } + } + + public static void afterRowCreate(RowWriteHandlerContext context) { + RowHandlerExecutionChain rowHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getRowHandlerExecutionChain(); + if (rowHandlerExecutionChain != null) { + rowHandlerExecutionChain.afterRowCreate(context); + } + } + + public static void afterRowDispose(RowWriteHandlerContext context) { + RowHandlerExecutionChain rowHandlerExecutionChain = ((AbstractWriteHolder)context.getWriteContext() + .currentWriteHolder()).getRowHandlerExecutionChain(); + if (rowHandlerExecutionChain != null) { + rowHandlerExecutionChain.afterRowDispose(context); + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilder.java new file mode 100644 index 0000000..c5eaff0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilder.java @@ -0,0 +1,79 @@ +package ai.chat2db.excel.write; + +import java.util.Collection; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.write.merge.OnceAbsoluteMergeStrategy; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.fill.FillConfig; + +/** + * @author jipengfei + */ +public interface ExcelBuilder { + + /** + * WorkBook increase value + * + * @param data + * java basic type or java model extend BaseModel + * @param writeSheet + * Write the sheet + * @deprecated please use{@link ExcelBuilder#addContent(Collection, WriteSheet, WriteTable)} + */ + @Deprecated + void addContent(Collection data, WriteSheet writeSheet); + + /** + * WorkBook increase value + * + * @param data + * java basic type or java model extend BaseModel + * @param writeSheet + * Write the sheet + * @param writeTable + * Write the table + */ + void addContent(Collection data, WriteSheet writeSheet, WriteTable writeTable); + + /** + * WorkBook fill value + * + * @param data + * @param fillConfig + * @param writeSheet + */ + void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet); + + /** + * Creates new cell range. Indexes are zero-based. + * + * @param firstRow + * Index of first row + * @param lastRow + * Index of last row (inclusive), must be equal to or larger than {@code firstRow} + * @param firstCol + * Index of first column + * @param lastCol + * Index of last column (inclusive), must be equal to or larger than {@code firstCol} + * @deprecated please use{@link OnceAbsoluteMergeStrategy} + */ + @Deprecated + void merge(int firstRow, int lastRow, int firstCol, int lastCol); + + /** + * Gets the written data + * + * @return + */ + WriteContext writeContext(); + + /** + * Close io + * + * @param onException + */ + void finish(boolean onException); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilderImpl.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilderImpl.java new file mode 100644 index 0000000..d1b7c27 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/ExcelBuilderImpl.java @@ -0,0 +1,113 @@ +package ai.chat2db.excel.write; + +import java.util.Collection; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.context.WriteContextImpl; +import ai.chat2db.excel.enums.WriteTypeEnum; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.write.executor.ExcelWriteAddExecutor; +import ai.chat2db.excel.write.executor.ExcelWriteFillExecutor; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.WriteWorkbook; +import ai.chat2db.excel.write.metadata.fill.FillConfig; + +import org.apache.poi.ss.util.CellRangeAddress; + +/** + * @author jipengfei + */ +public class ExcelBuilderImpl implements ExcelBuilder { + + private final WriteContext context; + private ExcelWriteFillExecutor excelWriteFillExecutor; + private ExcelWriteAddExecutor excelWriteAddExecutor; + + static { + // Create temporary cache directory at initialization time to avoid POI concurrent write bugs + FileUtils.createPoiFilesDirectory(); + } + + public ExcelBuilderImpl(WriteWorkbook writeWorkbook) { + try { + context = new WriteContextImpl(writeWorkbook); + } catch (RuntimeException e) { + finishOnException(); + throw e; + } catch (Throwable e) { + finishOnException(); + throw new ExcelGenerateException(e); + } + } + + @Override + public void addContent(Collection data, WriteSheet writeSheet) { + addContent(data, writeSheet, null); + } + + @Override + public void addContent(Collection data, WriteSheet writeSheet, WriteTable writeTable) { + try { + context.currentSheet(writeSheet, WriteTypeEnum.ADD); + context.currentTable(writeTable); + if (excelWriteAddExecutor == null) { + excelWriteAddExecutor = new ExcelWriteAddExecutor(context); + } + excelWriteAddExecutor.add(data); + } catch (RuntimeException e) { + finishOnException(); + throw e; + } catch (Throwable e) { + finishOnException(); + throw new ExcelGenerateException(e); + } + } + + @Override + public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) { + try { + if (context.writeWorkbookHolder().getTempTemplateInputStream() == null) { + throw new ExcelGenerateException("Calling the 'fill' method must use a template."); + } + if (context.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.CSV) { + throw new ExcelGenerateException("csv does not support filling data."); + } + context.currentSheet(writeSheet, WriteTypeEnum.FILL); + if (excelWriteFillExecutor == null) { + excelWriteFillExecutor = new ExcelWriteFillExecutor(context); + } + excelWriteFillExecutor.fill(data, fillConfig); + } catch (RuntimeException e) { + finishOnException(); + throw e; + } catch (Throwable e) { + finishOnException(); + throw new ExcelGenerateException(e); + } + } + + private void finishOnException() { + finish(true); + } + + @Override + public void finish(boolean onException) { + if (context != null) { + context.finish(onException); + } + } + + @Override + public void merge(int firstRow, int lastRow, int firstCol, int lastCol) { + CellRangeAddress cra = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol); + context.writeSheetHolder().getSheet().addMergedRegion(cra); + } + + @Override + public WriteContext writeContext() { + return context; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/AbstractExcelWriterParameterBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/AbstractExcelWriterParameterBuilder.java new file mode 100644 index 0000000..9ef1a94 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/AbstractExcelWriterParameterBuilder.java @@ -0,0 +1,136 @@ +package ai.chat2db.excel.write.builder; + +import java.util.ArrayList; +import java.util.Collection; + +import ai.chat2db.excel.write.handler.WriteHandler; +import ai.chat2db.excel.metadata.AbstractParameterBuilder; +import ai.chat2db.excel.write.metadata.WriteBasicParameter; + +/** + * Build ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractExcelWriterParameterBuilder extends AbstractParameterBuilder { + /** + * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. + * + * @param relativeHeadRowIndex + * @return + */ + public T relativeHeadRowIndex(Integer relativeHeadRowIndex) { + parameter().setRelativeHeadRowIndex(relativeHeadRowIndex); + return self(); + } + + /** + * Need Head + */ + public T needHead(Boolean needHead) { + parameter().setNeedHead(needHead); + return self(); + } + + /** + * Custom write handler + * + * @param writeHandler + * @return + */ + public T registerWriteHandler(WriteHandler writeHandler) { + if (parameter().getCustomWriteHandlerList() == null) { + parameter().setCustomWriteHandlerList(new ArrayList()); + } + parameter().getCustomWriteHandlerList().add(writeHandler); + return self(); + } + + /** + * Use the default style.Default is true. + * + * @param useDefaultStyle + * @return + */ + public T useDefaultStyle(Boolean useDefaultStyle) { + parameter().setUseDefaultStyle(useDefaultStyle); + return self(); + } + + /** + * Whether to automatically merge headers.Default is true. + * + * @param automaticMergeHead + * @return + */ + public T automaticMergeHead(Boolean automaticMergeHead) { + parameter().setAutomaticMergeHead(automaticMergeHead); + return self(); + } + + /** + * Ignore the custom columns. + */ + public T excludeColumnIndexes(Collection excludeColumnIndexes) { + parameter().setExcludeColumnIndexes(excludeColumnIndexes); + return self(); + } + + /** + * Ignore the custom columns. + * + * @deprecated use {@link #excludeColumnFieldNames(Collection)} + */ + public T excludeColumnFiledNames(Collection excludeColumnFieldNames) { + parameter().setExcludeColumnFieldNames(excludeColumnFieldNames); + return self(); + } + + /** + * Ignore the custom columns. + */ + public T excludeColumnFieldNames(Collection excludeColumnFieldNames) { + parameter().setExcludeColumnFieldNames(excludeColumnFieldNames); + return self(); + } + + /** + * Only output the custom columns. + */ + public T includeColumnIndexes(Collection includeColumnIndexes) { + parameter().setIncludeColumnIndexes(includeColumnIndexes); + return self(); + } + + /** + * Only output the custom columns. + * + * @deprecated use {@link #includeColumnFieldNames(Collection)} spelling mistake + */ + @Deprecated + public T includeColumnFiledNames(Collection includeColumnFieldNames) { + parameter().setIncludeColumnFieldNames(includeColumnFieldNames); + return self(); + } + + /** + * Only output the custom columns. + */ + public T includeColumnFieldNames(Collection includeColumnFieldNames) { + parameter().setIncludeColumnFieldNames(includeColumnFieldNames); + return self(); + } + + /** + * Data will be order by {@link #includeColumnFieldNames} or {@link #includeColumnIndexes}. + * + * default is false. + * + * @since 3.3.0 + **/ + public T orderByIncludeColumn(Boolean orderByIncludeColumn) { + parameter().setOrderByIncludeColumn(orderByIncludeColumn); + return self(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterBuilder.java new file mode 100644 index 0000000..7c6d2c8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterBuilder.java @@ -0,0 +1,164 @@ +package ai.chat2db.excel.write.builder; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.write.metadata.WriteWorkbook; + +/** + * Build ExcelBuilder + * + * @author Jiaju Zhuang + */ +public class ExcelWriterBuilder extends AbstractExcelWriterParameterBuilder { + /** + * Workbook + */ + private final WriteWorkbook writeWorkbook; + + public ExcelWriterBuilder() { + this.writeWorkbook = new WriteWorkbook(); + } + + /** + * Default true + * + * @param autoCloseStream + * @return + */ + public ExcelWriterBuilder autoCloseStream(Boolean autoCloseStream) { + writeWorkbook.setAutoCloseStream(autoCloseStream); + return this; + } + + /** + * Whether the encryption. + *

+ * WARRING:Encryption is when the entire file is read into memory, so it is very memory intensive. + * + * @param password + * @return + */ + public ExcelWriterBuilder password(String password) { + writeWorkbook.setPassword(password); + return this; + } + + /** + * Write excel in memory. Default false, the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + public ExcelWriterBuilder inMemory(Boolean inMemory) { + writeWorkbook.setInMemory(inMemory); + return this; + } + + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + public ExcelWriterBuilder writeExcelOnException(Boolean writeExcelOnException) { + writeWorkbook.setWriteExcelOnException(writeExcelOnException); + return this; + } + + public ExcelWriterBuilder excelType(ExcelTypeEnum excelType) { + writeWorkbook.setExcelType(excelType); + return this; + } + + public ExcelWriterBuilder file(OutputStream outputStream) { + writeWorkbook.setOutputStream(outputStream); + return this; + } + + public ExcelWriterBuilder file(File outputFile) { + writeWorkbook.setFile(outputFile); + return this; + } + + public ExcelWriterBuilder file(String outputPathName) { + return file(new File(outputPathName)); + } + + /** + * charset. + * Only work on the CSV file + */ + public ExcelWriterBuilder charset(Charset charset) { + writeWorkbook.setCharset(charset); + return this; + } + + /** + * Set the encoding prefix in the csv file, otherwise the office may open garbled characters. + * Default true. + */ + public ExcelWriterBuilder withBom(Boolean withBom) { + writeWorkbook.setWithBom(withBom); + return this; + } + + /** + * Template file. + * This file is read into memory, excessive cases can lead to OOM. + */ + public ExcelWriterBuilder withTemplate(InputStream templateInputStream) { + writeWorkbook.setTemplateInputStream(templateInputStream); + return this; + } + + /** + * Template file. + * This file is read into memory, excessive cases can lead to OOM. + */ + public ExcelWriterBuilder withTemplate(File templateFile) { + writeWorkbook.setTemplateFile(templateFile); + return this; + } + + /** + * Template file. + * This file is read into memory, excessive cases can lead to OOM. + */ + public ExcelWriterBuilder withTemplate(String pathName) { + return withTemplate(new File(pathName)); + } + + public ExcelWriter build() { + return new ExcelWriter(writeWorkbook); + } + + public ExcelWriterSheetBuilder sheet() { + return sheet(null, null); + } + + public ExcelWriterSheetBuilder sheet(Integer sheetNo) { + return sheet(sheetNo, null); + } + + public ExcelWriterSheetBuilder sheet(String sheetName) { + return sheet(null, sheetName); + } + + public ExcelWriterSheetBuilder sheet(Integer sheetNo, String sheetName) { + ExcelWriter excelWriter = build(); + ExcelWriterSheetBuilder excelWriterSheetBuilder = new ExcelWriterSheetBuilder(excelWriter); + if (sheetNo != null) { + excelWriterSheetBuilder.sheetNo(sheetNo); + } + if (sheetName != null) { + excelWriterSheetBuilder.sheetName(sheetName); + } + return excelWriterSheetBuilder; + } + + @Override + protected WriteWorkbook parameter() { + return writeWorkbook; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterSheetBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterSheetBuilder.java new file mode 100644 index 0000000..8fad3ab --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterSheetBuilder.java @@ -0,0 +1,107 @@ +package ai.chat2db.excel.write.builder; + +import java.util.Collection; +import java.util.function.Supplier; + +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.fill.FillConfig; + +/** + * Build sheet + * + * @author Jiaju Zhuang + */ +public class ExcelWriterSheetBuilder extends AbstractExcelWriterParameterBuilder { + private ExcelWriter excelWriter; + /** + * Sheet + */ + private final WriteSheet writeSheet; + + public ExcelWriterSheetBuilder() { + this.writeSheet = new WriteSheet(); + } + + public ExcelWriterSheetBuilder(ExcelWriter excelWriter) { + this.writeSheet = new WriteSheet(); + this.excelWriter = excelWriter; + } + + /** + * Starting from 0 + * + * @param sheetNo + * @return + */ + public ExcelWriterSheetBuilder sheetNo(Integer sheetNo) { + writeSheet.setSheetNo(sheetNo); + return this; + } + + /** + * sheet name + * + * @param sheetName + * @return + */ + public ExcelWriterSheetBuilder sheetName(String sheetName) { + writeSheet.setSheetName(sheetName); + return this; + } + + public WriteSheet build() { + return writeSheet; + } + + public void doWrite(Collection data) { + if (excelWriter == null) { + throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method"); + } + excelWriter.write(data, build()); + excelWriter.finish(); + } + + public void doFill(Object data) { + doFill(data, null); + } + + public void doFill(Object data, FillConfig fillConfig) { + if (excelWriter == null) { + throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method"); + } + excelWriter.fill(data, fillConfig, build()); + excelWriter.finish(); + } + + public void doWrite(Supplier> supplier) { + doWrite(supplier.get()); + } + + public void doFill(Supplier supplier) { + doFill(supplier.get()); + } + + public void doFill(Supplier supplier, FillConfig fillConfig) { + doFill(supplier.get(), fillConfig); + } + + public ExcelWriterTableBuilder table() { + return table(null); + } + + public ExcelWriterTableBuilder table(Integer tableNo) { + ExcelWriterTableBuilder excelWriterTableBuilder = new ExcelWriterTableBuilder(excelWriter, build()); + if (tableNo != null) { + excelWriterTableBuilder.tableNo(tableNo); + } + return excelWriterTableBuilder; + } + + @Override + protected WriteSheet parameter() { + return writeSheet; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterTableBuilder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterTableBuilder.java new file mode 100644 index 0000000..bb78871 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/builder/ExcelWriterTableBuilder.java @@ -0,0 +1,67 @@ +package ai.chat2db.excel.write.builder; + +import java.util.Collection; +import java.util.function.Supplier; + +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; + +/** + * Build sheet + * + * @author Jiaju Zhuang + */ +public class ExcelWriterTableBuilder extends AbstractExcelWriterParameterBuilder { + + private ExcelWriter excelWriter; + + private WriteSheet writeSheet; + /** + * table + */ + private final WriteTable writeTable; + + public ExcelWriterTableBuilder() { + this.writeTable = new WriteTable(); + } + + public ExcelWriterTableBuilder(ExcelWriter excelWriter, WriteSheet writeSheet) { + this.excelWriter = excelWriter; + this.writeSheet = writeSheet; + this.writeTable = new WriteTable(); + } + + /** + * Starting from 0 + * + * @param tableNo + * @return + */ + public ExcelWriterTableBuilder tableNo(Integer tableNo) { + writeTable.setTableNo(tableNo); + return this; + } + + public WriteTable build() { + return writeTable; + } + + public void doWrite(Collection data) { + if (excelWriter == null) { + throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet().table()' to call this method"); + } + excelWriter.write(data, writeSheet, build()); + excelWriter.finish(); + } + + public void doWrite(Supplier> supplier) { + doWrite(supplier.get()); + } + + @Override + protected WriteTable parameter() { + return writeTable; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/AbstractExcelWriteExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/AbstractExcelWriteExecutor.java new file mode 100644 index 0000000..6ef18aa --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/AbstractExcelWriteExecutor.java @@ -0,0 +1,343 @@ +package ai.chat2db.excel.write.executor; + +import java.util.List; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; +import ai.chat2db.excel.converters.NullableObjectConverter; +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.exception.ExcelWriteDataConvertException; +import ai.chat2db.excel.metadata.data.CommentData; +import ai.chat2db.excel.metadata.data.FormulaData; +import ai.chat2db.excel.metadata.data.HyperlinkData; +import ai.chat2db.excel.metadata.data.ImageData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.FileTypeUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.StyleUtil; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.util.WriteHandlerUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; + +/** + * Excel write Executor + * + * @author Jiaju Zhuang + */ +public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { + protected WriteContext writeContext; + + public AbstractExcelWriteExecutor(WriteContext writeContext) { + this.writeContext = writeContext; + } + + /** + * Transform the data and then to set into the cell + * + * @param cellWriteHandlerContext context + */ + protected void converterAndSet(CellWriteHandlerContext cellWriteHandlerContext) { + + WriteCellData cellData = convert(cellWriteHandlerContext); + cellWriteHandlerContext.setCellDataList(ListUtils.newArrayList(cellData)); + cellWriteHandlerContext.setFirstCellData(cellData); + + WriteHandlerUtils.afterCellDataConverted(cellWriteHandlerContext); + + // Fill in picture information + fillImage(cellWriteHandlerContext, cellData.getImageDataList()); + + // Fill in comment information + fillComment(cellWriteHandlerContext, cellData.getCommentData()); + + // Fill in hyper link information + fillHyperLink(cellWriteHandlerContext, cellData.getHyperlinkData()); + + // Fill in formula information + fillFormula(cellWriteHandlerContext, cellData.getFormulaData()); + + // Fill index + cellData.setRowIndex(cellWriteHandlerContext.getRowIndex()); + cellData.setColumnIndex(cellWriteHandlerContext.getColumnIndex()); + + if (cellData.getType() == null) { + cellData.setType(CellDataTypeEnum.EMPTY); + } + Cell cell = cellWriteHandlerContext.getCell(); + switch (cellData.getType()) { + case STRING: + cell.setCellValue(cellData.getStringValue()); + return; + case BOOLEAN: + cell.setCellValue(cellData.getBooleanValue()); + return; + case NUMBER: + cell.setCellValue(cellData.getNumberValue().doubleValue()); + return; + case DATE: + cell.setCellValue(cellData.getDateValue()); + return; + case RICH_TEXT_STRING: + cell.setCellValue(StyleUtil + .buildRichTextString(writeContext.writeWorkbookHolder(), cellData.getRichTextStringDataValue())); + return; + case EMPTY: + return; + default: + throw new ExcelWriteDataConvertException(cellWriteHandlerContext, + "Not supported data:" + cellWriteHandlerContext.getOriginalValue() + " return type:" + + cellData.getType() + + "at row:" + cellWriteHandlerContext.getRowIndex()); + } + + } + + private void fillFormula(CellWriteHandlerContext cellWriteHandlerContext, FormulaData formulaData) { + if (formulaData == null) { + return; + } + Cell cell = cellWriteHandlerContext.getCell(); + if (formulaData.getFormulaValue() != null) { + cell.setCellFormula(formulaData.getFormulaValue()); + } + } + + private void fillHyperLink(CellWriteHandlerContext cellWriteHandlerContext, HyperlinkData hyperlinkData) { + if (hyperlinkData == null) { + return; + } + Integer rowIndex = cellWriteHandlerContext.getRowIndex(); + Integer columnIndex = cellWriteHandlerContext.getColumnIndex(); + Workbook workbook = cellWriteHandlerContext.getWriteWorkbookHolder().getWorkbook(); + Cell cell = cellWriteHandlerContext.getCell(); + + CreationHelper helper = workbook.getCreationHelper(); + Hyperlink hyperlink = helper.createHyperlink(StyleUtil.getHyperlinkType(hyperlinkData.getHyperlinkType())); + hyperlink.setAddress(hyperlinkData.getAddress()); + hyperlink.setFirstRow(StyleUtil.getCellCoordinate(rowIndex, hyperlinkData.getFirstRowIndex(), + hyperlinkData.getRelativeFirstRowIndex())); + hyperlink.setFirstColumn(StyleUtil.getCellCoordinate(columnIndex, hyperlinkData.getFirstColumnIndex(), + hyperlinkData.getRelativeFirstColumnIndex())); + hyperlink.setLastRow(StyleUtil.getCellCoordinate(rowIndex, hyperlinkData.getLastRowIndex(), + hyperlinkData.getRelativeLastRowIndex())); + hyperlink.setLastColumn(StyleUtil.getCellCoordinate(columnIndex, hyperlinkData.getLastColumnIndex(), + hyperlinkData.getRelativeLastColumnIndex())); + cell.setHyperlink(hyperlink); + } + + private void fillComment(CellWriteHandlerContext cellWriteHandlerContext, CommentData commentData) { + if (commentData == null) { + return; + } + ClientAnchor anchor; + Integer rowIndex = cellWriteHandlerContext.getRowIndex(); + Integer columnIndex = cellWriteHandlerContext.getColumnIndex(); + Sheet sheet = cellWriteHandlerContext.getWriteSheetHolder().getSheet(); + Cell cell = cellWriteHandlerContext.getCell(); + + if (writeContext.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.XLSX) { + anchor = new XSSFClientAnchor(StyleUtil.getCoordinate(commentData.getLeft()), + StyleUtil.getCoordinate(commentData.getTop()), + StyleUtil.getCoordinate(commentData.getRight()), + StyleUtil.getCoordinate(commentData.getBottom()), + StyleUtil.getCellCoordinate(columnIndex, commentData.getFirstColumnIndex(), + commentData.getRelativeFirstColumnIndex()), + StyleUtil.getCellCoordinate(rowIndex, commentData.getFirstRowIndex(), + commentData.getRelativeFirstRowIndex()), + StyleUtil.getCellCoordinate(columnIndex, commentData.getLastColumnIndex(), + commentData.getRelativeLastColumnIndex()) + 1, + StyleUtil.getCellCoordinate(rowIndex, commentData.getLastRowIndex(), + commentData.getRelativeLastRowIndex()) + 1); + } else { + anchor = new HSSFClientAnchor(StyleUtil.getCoordinate(commentData.getLeft()), + StyleUtil.getCoordinate(commentData.getTop()), + StyleUtil.getCoordinate(commentData.getRight()), + StyleUtil.getCoordinate(commentData.getBottom()), + (short)StyleUtil.getCellCoordinate(columnIndex, commentData.getFirstColumnIndex(), + commentData.getRelativeFirstColumnIndex()), + StyleUtil.getCellCoordinate(rowIndex, commentData.getFirstRowIndex(), + commentData.getRelativeFirstRowIndex()), + (short)(StyleUtil.getCellCoordinate(columnIndex, commentData.getLastColumnIndex(), + commentData.getRelativeLastColumnIndex()) + 1), + StyleUtil.getCellCoordinate(rowIndex, commentData.getLastRowIndex(), + commentData.getRelativeLastRowIndex()) + 1); + } + + Comment comment = sheet.createDrawingPatriarch().createCellComment(anchor); + if (commentData.getRichTextStringData() != null) { + comment.setString( + StyleUtil.buildRichTextString(writeContext.writeWorkbookHolder(), commentData.getRichTextStringData())); + } + if (commentData.getAuthor() != null) { + comment.setAuthor(commentData.getAuthor()); + } + cell.setCellComment(comment); + } + + protected void fillImage(CellWriteHandlerContext cellWriteHandlerContext, List imageDataList) { + if (CollectionUtils.isEmpty(imageDataList)) { + return; + } + Integer rowIndex = cellWriteHandlerContext.getRowIndex(); + Integer columnIndex = cellWriteHandlerContext.getColumnIndex(); + Sheet sheet = cellWriteHandlerContext.getWriteSheetHolder().getSheet(); + Workbook workbook = cellWriteHandlerContext.getWriteWorkbookHolder().getWorkbook(); + + Drawing drawing = sheet.getDrawingPatriarch(); + if (drawing == null) { + drawing = sheet.createDrawingPatriarch(); + } + CreationHelper helper = sheet.getWorkbook().getCreationHelper(); + for (ImageData imageData : imageDataList) { + int index = workbook.addPicture(imageData.getImage(), + FileTypeUtils.getImageTypeFormat(imageData.getImage())); + ClientAnchor anchor = helper.createClientAnchor(); + if (imageData.getTop() != null) { + anchor.setDy1(StyleUtil.getCoordinate(imageData.getTop())); + } + if (imageData.getRight() != null) { + anchor.setDx2(-StyleUtil.getCoordinate(imageData.getRight())); + } + if (imageData.getBottom() != null) { + anchor.setDy2(-StyleUtil.getCoordinate(imageData.getBottom())); + } + if (imageData.getLeft() != null) { + anchor.setDx1(StyleUtil.getCoordinate(imageData.getLeft())); + } + anchor.setRow1(StyleUtil.getCellCoordinate(rowIndex, imageData.getFirstRowIndex(), + imageData.getRelativeFirstRowIndex())); + anchor.setCol1(StyleUtil.getCellCoordinate(columnIndex, imageData.getFirstColumnIndex(), + imageData.getRelativeFirstColumnIndex())); + anchor.setRow2(StyleUtil.getCellCoordinate(rowIndex, imageData.getLastRowIndex(), + imageData.getRelativeLastRowIndex()) + 1); + anchor.setCol2(StyleUtil.getCellCoordinate(columnIndex, imageData.getLastColumnIndex(), + imageData.getRelativeLastColumnIndex()) + 1); + if (imageData.getAnchorType() != null) { + anchor.setAnchorType(imageData.getAnchorType().getValue()); + } + drawing.createPicture(anchor, index); + } + } + + protected WriteCellData convert(CellWriteHandlerContext cellWriteHandlerContext) { + // This means that the user has defined the data. + if (cellWriteHandlerContext.getOriginalFieldClass() == WriteCellData.class) { + if (cellWriteHandlerContext.getOriginalValue() == null) { + return new WriteCellData<>(CellDataTypeEnum.EMPTY); + } + WriteCellData cellDataValue = (WriteCellData)cellWriteHandlerContext.getOriginalValue(); + if (cellDataValue.getType() != null) { + // Configuration information may not be read here + fillProperty(cellDataValue, cellWriteHandlerContext.getExcelContentProperty()); + + return cellDataValue; + } else { + if (cellDataValue.getData() == null) { + cellDataValue.setType(CellDataTypeEnum.EMPTY); + return cellDataValue; + } + } + WriteCellData cellDataReturn = doConvert(cellWriteHandlerContext); + + if (cellDataValue.getImageDataList() != null) { + cellDataReturn.setImageDataList(cellDataValue.getImageDataList()); + } + if (cellDataValue.getCommentData() != null) { + cellDataReturn.setCommentData(cellDataValue.getCommentData()); + } + if (cellDataValue.getHyperlinkData() != null) { + cellDataReturn.setHyperlinkData(cellDataValue.getHyperlinkData()); + } + // The formula information is subject to user input + if (cellDataValue.getFormulaData() != null) { + cellDataReturn.setFormulaData(cellDataValue.getFormulaData()); + } + if (cellDataValue.getWriteCellStyle() != null) { + cellDataReturn.setWriteCellStyle(cellDataValue.getWriteCellStyle()); + } + return cellDataReturn; + } + return doConvert(cellWriteHandlerContext); + } + + private void fillProperty(WriteCellData cellDataValue, ExcelContentProperty excelContentProperty) { + switch (cellDataValue.getType()) { + case DATE: + String dateFormat = null; + if (excelContentProperty != null && excelContentProperty.getDateTimeFormatProperty() != null) { + dateFormat = excelContentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellDataValue, dateFormat, DateUtils.defaultDateFormat); + return; + case NUMBER: + String numberFormat = null; + if (excelContentProperty != null && excelContentProperty.getNumberFormatProperty() != null) { + numberFormat = excelContentProperty.getNumberFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellDataValue, numberFormat, null); + return; + default: + return; + } + } + + private WriteCellData doConvert(CellWriteHandlerContext cellWriteHandlerContext) { + ExcelContentProperty excelContentProperty = cellWriteHandlerContext.getExcelContentProperty(); + + Converter converter = null; + if (excelContentProperty != null) { + converter = excelContentProperty.getConverter(); + } + if (converter == null) { + // csv is converted to string by default + if (writeContext.writeWorkbookHolder().getExcelType() == ExcelTypeEnum.CSV) { + cellWriteHandlerContext.setTargetCellDataType(CellDataTypeEnum.STRING); + } + converter = writeContext.currentWriteHolder().converterMap().get( + ConverterKeyBuild.buildKey(cellWriteHandlerContext.getOriginalFieldClass(), + cellWriteHandlerContext.getTargetCellDataType())); + } + if (cellWriteHandlerContext.getOriginalValue() == null && !(converter instanceof NullableObjectConverter)) { + return new WriteCellData<>(CellDataTypeEnum.EMPTY); + } + if (converter == null) { + throw new ExcelWriteDataConvertException(cellWriteHandlerContext, + "Can not find 'Converter' support class " + cellWriteHandlerContext.getOriginalFieldClass() + .getSimpleName() + "."); + } + WriteCellData cellData; + try { + cellData = ((Converter)converter).convertToExcelData( + new WriteConverterContext<>(cellWriteHandlerContext.getOriginalValue(), excelContentProperty, + writeContext)); + } catch (Exception e) { + throw new ExcelWriteDataConvertException(cellWriteHandlerContext, + "Convert data:" + cellWriteHandlerContext.getOriginalValue() + " error, at row:" + + cellWriteHandlerContext.getRowIndex(), e); + } + if (cellData == null || cellData.getType() == null) { + throw new ExcelWriteDataConvertException(cellWriteHandlerContext, + "Convert data:" + cellWriteHandlerContext.getOriginalValue() + + " return is null or return type is null, at row:" + + cellWriteHandlerContext.getRowIndex()); + } + return cellData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteAddExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteAddExecutor.java new file mode 100644 index 0000000..44ff1aa --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteAddExecutor.java @@ -0,0 +1,215 @@ +package ai.chat2db.excel.write.executor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.metadata.FieldCache; +import ai.chat2db.excel.metadata.FieldWrapper; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.support.cglib.beans.BeanMap; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.FieldUtils; +import ai.chat2db.excel.util.WorkBookUtil; +import ai.chat2db.excel.util.WriteHandlerUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.metadata.CollectionRowData; +import ai.chat2db.excel.write.metadata.MapRowData; +import ai.chat2db.excel.write.metadata.RowData; +import ai.chat2db.excel.write.metadata.holder.WriteHolder; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; + +/** + * Add the data into excel + * + * @author Jiaju Zhuang + */ +public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { + + public ExcelWriteAddExecutor(WriteContext writeContext) { + super(writeContext); + } + + public void add(Collection data) { + if (CollectionUtils.isEmpty(data)) { + data = new ArrayList<>(); + } + WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder(); + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { + newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex(); + } + int relativeRowIndex = 0; + for (Object oneRowData : data) { + int lastRowIndex = relativeRowIndex + newRowIndex; + addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex); + relativeRowIndex++; + } + } + + private void addOneRowOfDataToExcel(Object oneRowData, int rowIndex, int relativeRowIndex) { + if (oneRowData == null) { + return; + } + RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext, + rowIndex, relativeRowIndex, Boolean.FALSE); + WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext); + + Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), rowIndex); + rowWriteHandlerContext.setRow(row); + + WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext); + + if (oneRowData instanceof Collection) { + addBasicTypeToExcel(new CollectionRowData((Collection)oneRowData), row, rowIndex, relativeRowIndex); + } else if (oneRowData instanceof Map) { + addBasicTypeToExcel(new MapRowData((Map)oneRowData), row, rowIndex, relativeRowIndex); + } else { + addJavaObjectToExcel(oneRowData, row, rowIndex, relativeRowIndex); + } + + WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext); + } + + private void addBasicTypeToExcel(RowData oneRowData, Row row, int rowIndex, int relativeRowIndex) { + if (oneRowData.isEmpty()) { + return; + } + Map headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap(); + int dataIndex = 0; + int maxCellIndex = -1; + for (Map.Entry entry : headMap.entrySet()) { + if (dataIndex >= oneRowData.size()) { + return; + } + int columnIndex = entry.getKey(); + Head head = entry.getValue(); + doAddBasicTypeToExcel(oneRowData, head, row, rowIndex, relativeRowIndex, dataIndex++, columnIndex); + maxCellIndex = Math.max(maxCellIndex, columnIndex); + } + // Finish + if (dataIndex >= oneRowData.size()) { + return; + } + // fix https://github.com/alibaba/easyexcel/issues/1702 + // If there is data, it is written to the next cell + maxCellIndex++; + + int size = oneRowData.size() - dataIndex; + for (int i = 0; i < size; i++) { + doAddBasicTypeToExcel(oneRowData, null, row, rowIndex, relativeRowIndex, dataIndex++, maxCellIndex++); + } + } + + private void doAddBasicTypeToExcel(RowData oneRowData, Head head, Row row, int rowIndex, int relativeRowIndex, + int dataIndex, int columnIndex) { + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(null, + writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadClazz(), + head == null ? null : head.getFieldName(), writeContext.currentWriteHolder()); + + CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext(writeContext, + row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty); + WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext); + + Cell cell = WorkBookUtil.createCell(row, columnIndex); + cellWriteHandlerContext.setCell(cell); + + WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext); + + cellWriteHandlerContext.setOriginalValue(oneRowData.get(dataIndex)); + cellWriteHandlerContext.setOriginalFieldClass( + FieldUtils.getFieldClass(cellWriteHandlerContext.getOriginalValue())); + converterAndSet(cellWriteHandlerContext); + + WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext); + } + + private void addJavaObjectToExcel(Object oneRowData, Row row, int rowIndex, int relativeRowIndex) { + WriteHolder currentWriteHolder = writeContext.currentWriteHolder(); + BeanMap beanMap = BeanMapUtils.create(oneRowData); + // Bean the contains of the Map Key method with poor performance,So to create a keySet here + Set beanKeySet = new HashSet<>(beanMap.keySet()); + Set beanMapHandledSet = new HashSet<>(); + int maxCellIndex = -1; + // If it's a class it needs to be cast by type + if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) { + Map headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap(); + for (Map.Entry entry : headMap.entrySet()) { + int columnIndex = entry.getKey(); + Head head = entry.getValue(); + String name = head.getFieldName(); + if (!beanKeySet.contains(name)) { + continue; + } + + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap, + currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), name, currentWriteHolder); + CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext( + writeContext, row, rowIndex, head, columnIndex, relativeRowIndex, Boolean.FALSE, + excelContentProperty); + WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext); + + Cell cell = WorkBookUtil.createCell(row, columnIndex); + cellWriteHandlerContext.setCell(cell); + + WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext); + + cellWriteHandlerContext.setOriginalValue(beanMap.get(name)); + cellWriteHandlerContext.setOriginalFieldClass(head.getField().getType()); + converterAndSet(cellWriteHandlerContext); + + WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext); + + beanMapHandledSet.add(name); + maxCellIndex = Math.max(maxCellIndex, columnIndex); + } + } + // Finish + if (beanMapHandledSet.size() == beanMap.size()) { + return; + } + maxCellIndex++; + + FieldCache fieldCache = ClassUtils.declaredFields(oneRowData.getClass(), writeContext.currentWriteHolder()); + for (Map.Entry entry : fieldCache.getSortedFieldMap().entrySet()) { + FieldWrapper field = entry.getValue(); + String fieldName = field.getFieldName(); + boolean uselessData = !beanKeySet.contains(fieldName) || beanMapHandledSet.contains(fieldName); + if (uselessData) { + continue; + } + Object value = beanMap.get(fieldName); + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(beanMap, + currentWriteHolder.excelWriteHeadProperty().getHeadClazz(), fieldName, currentWriteHolder); + CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext( + writeContext, row, rowIndex, null, maxCellIndex, relativeRowIndex, Boolean.FALSE, excelContentProperty); + WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext); + + // fix https://github.com/alibaba/easyexcel/issues/1870 + // If there is data, it is written to the next cell + Cell cell = WorkBookUtil.createCell(row, maxCellIndex); + cellWriteHandlerContext.setCell(cell); + + WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext); + + cellWriteHandlerContext.setOriginalValue(value); + cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(beanMap, fieldName, value)); + converterAndSet(cellWriteHandlerContext); + + WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext); + maxCellIndex++; + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteExecutor.java new file mode 100644 index 0000000..1239a68 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteExecutor.java @@ -0,0 +1,9 @@ +package ai.chat2db.excel.write.executor; + +/** + * Excel write Executor + * + * @author Jiaju Zhuang + */ +public interface ExcelWriteExecutor { +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteFillExecutor.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteFillExecutor.java new file mode 100644 index 0000000..ebf2a03 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/executor/ExcelWriteFillExecutor.java @@ -0,0 +1,633 @@ +package ai.chat2db.excel.write.executor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.enums.WriteDirectionEnum; +import ai.chat2db.excel.enums.WriteTemplateAnalysisCellTypeEnum; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.FieldUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.util.WriteHandlerUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.metadata.fill.AnalysisCell; +import ai.chat2db.excel.write.metadata.fill.FillConfig; +import ai.chat2db.excel.write.metadata.fill.FillWrapper; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; + +import ai.chat2db.excel.util.PoiUtils; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * Fill the data into excel + * + * @author Jiaju Zhuang + */ +public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { + + private static final String ESCAPE_FILL_PREFIX = "\\\\\\{"; + private static final String ESCAPE_FILL_SUFFIX = "\\\\\\}"; + private static final String FILL_PREFIX = "{"; + private static final String FILL_SUFFIX = "}"; + private static final char IGNORE_CHAR = '\\'; + private static final String COLLECTION_PREFIX = "."; + /** + * Fields to replace in the template + */ + private final Map> templateAnalysisCache = MapUtils.newHashMap(); + /** + * Collection fields to replace in the template + */ + private final Map> templateCollectionAnalysisCache = MapUtils.newHashMap(); + /** + * Style cache for collection fields + */ + private final Map> collectionFieldStyleCache + = MapUtils.newHashMap(); + /** + * Row height cache for collection + */ + private final Map collectionRowHeightCache = MapUtils.newHashMap(); + /** + * Last index cache for collection fields + */ + private final Map> collectionLastIndexCache = MapUtils.newHashMap(); + + private final Map relativeRowIndexMap = MapUtils.newHashMap(); + /** + * The unique data encoding for this fill + */ + private UniqueDataFlagKey currentUniqueDataFlag; + + public ExcelWriteFillExecutor(WriteContext writeContext) { + super(writeContext); + } + + public void fill(Object data, FillConfig fillConfig) { + if (data == null) { + data = new HashMap(16); + } + if (fillConfig == null) { + fillConfig = FillConfig.builder().build(); + } + fillConfig.init(); + + Object realData; + // The data prefix that is populated this time + String currentDataPrefix; + + if (data instanceof FillWrapper) { + FillWrapper fillWrapper = (FillWrapper)data; + currentDataPrefix = fillWrapper.getName(); + realData = fillWrapper.getCollectionData(); + } else { + realData = data; + currentDataPrefix = null; + } + currentUniqueDataFlag = uniqueDataFlag(writeContext.writeSheetHolder(), currentDataPrefix); + + // processing data + if (realData instanceof Collection) { + List analysisCellList = readTemplateData(templateCollectionAnalysisCache); + Collection collectionData = (Collection)realData; + if (CollectionUtils.isEmpty(collectionData)) { + return; + } + Iterator iterator = collectionData.iterator(); + if (WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection()) && fillConfig.getForceNewRow()) { + shiftRows(collectionData.size(), analysisCellList); + } + while (iterator.hasNext()) { + doFill(analysisCellList, iterator.next(), fillConfig, getRelativeRowIndex()); + } + } else { + doFill(readTemplateData(templateAnalysisCache), realData, fillConfig, null); + } + } + + private void shiftRows(int size, List analysisCellList) { + if (CollectionUtils.isEmpty(analysisCellList)) { + return; + } + int maxRowIndex = 0; + Map collectionLastIndexMap = collectionLastIndexCache.get(currentUniqueDataFlag); + for (AnalysisCell analysisCell : analysisCellList) { + if (collectionLastIndexMap != null) { + Integer lastRowIndex = collectionLastIndexMap.get(analysisCell); + if (lastRowIndex != null) { + if (lastRowIndex > maxRowIndex) { + maxRowIndex = lastRowIndex; + } + continue; + } + } + if (analysisCell.getRowIndex() > maxRowIndex) { + maxRowIndex = analysisCell.getRowIndex(); + } + } + Sheet cachedSheet = writeContext.writeSheetHolder().getCachedSheet(); + int lastRowIndex = cachedSheet.getLastRowNum(); + if (maxRowIndex >= lastRowIndex) { + return; + } + Sheet sheet = writeContext.writeSheetHolder().getCachedSheet(); + int number = size; + if (collectionLastIndexMap == null) { + number--; + } + if (number <= 0) { + return; + } + sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number, true, false); + + // The current data is greater than unity rowindex increase + increaseRowIndex(templateAnalysisCache, number, maxRowIndex); + increaseRowIndex(templateCollectionAnalysisCache, number, maxRowIndex); + } + + private void increaseRowIndex(Map> templateAnalysisCache, int number, + int maxRowIndex) { + for (Map.Entry> entry : templateAnalysisCache.entrySet()) { + UniqueDataFlagKey uniqueDataFlagKey = entry.getKey(); + if (!Objects.equals(currentUniqueDataFlag.getSheetNo(), uniqueDataFlagKey.getSheetNo()) || !Objects.equals( + currentUniqueDataFlag.getSheetName(), uniqueDataFlagKey.getSheetName())) { + continue; + } + for (AnalysisCell analysisCell : entry.getValue()) { + if (analysisCell.getRowIndex() > maxRowIndex) { + analysisCell.setRowIndex(analysisCell.getRowIndex() + number); + } + } + } + } + + private void doFill(List analysisCellList, Object oneRowData, FillConfig fillConfig, + Integer relativeRowIndex) { + if (CollectionUtils.isEmpty(analysisCellList) || oneRowData == null) { + return; + } + Map dataMap; + if (oneRowData instanceof Map) { + dataMap = (Map)oneRowData; + } else { + dataMap = BeanMapUtils.create(oneRowData); + } + Set dataKeySet = new HashSet<>(dataMap.keySet()); + + RowWriteHandlerContext rowWriteHandlerContext = WriteHandlerUtils.createRowWriteHandlerContext(writeContext, + null, relativeRowIndex, Boolean.FALSE); + + for (AnalysisCell analysisCell : analysisCellList) { + CellWriteHandlerContext cellWriteHandlerContext = WriteHandlerUtils.createCellWriteHandlerContext( + writeContext, null, analysisCell.getRowIndex(), null, analysisCell.getColumnIndex(), + relativeRowIndex, Boolean.FALSE, ExcelContentProperty.EMPTY); + + if (analysisCell.getOnlyOneVariable()) { + String variable = analysisCell.getVariableList().get(0); + if (!dataKeySet.contains(variable)) { + continue; + } + Object value = dataMap.get(variable); + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(dataMap, + writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadClazz(), variable, + writeContext.currentWriteHolder()); + cellWriteHandlerContext.setExcelContentProperty(excelContentProperty); + + createCell(analysisCell, fillConfig, cellWriteHandlerContext, rowWriteHandlerContext); + cellWriteHandlerContext.setOriginalValue(value); + cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(dataMap, variable, value)); + + converterAndSet(cellWriteHandlerContext); + WriteCellData cellData = cellWriteHandlerContext.getFirstCellData(); + + // Restyle + if (fillConfig.getAutoStyle()) { + Optional.ofNullable(collectionFieldStyleCache.get(currentUniqueDataFlag)) + .map(collectionFieldStyleMap -> collectionFieldStyleMap.get(analysisCell)) + .ifPresent(cellData::setOriginCellStyle); + } + } else { + StringBuilder cellValueBuild = new StringBuilder(); + int index = 0; + List> cellDataList = new ArrayList<>(); + + cellWriteHandlerContext.setExcelContentProperty(ExcelContentProperty.EMPTY); + cellWriteHandlerContext.setIgnoreFillStyle(Boolean.TRUE); + + createCell(analysisCell, fillConfig, cellWriteHandlerContext, rowWriteHandlerContext); + Cell cell = cellWriteHandlerContext.getCell(); + + for (String variable : analysisCell.getVariableList()) { + cellValueBuild.append(analysisCell.getPrepareDataList().get(index++)); + if (!dataKeySet.contains(variable)) { + continue; + } + Object value = dataMap.get(variable); + ExcelContentProperty excelContentProperty = ClassUtils.declaredExcelContentProperty(dataMap, + writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadClazz(), variable, + writeContext.currentWriteHolder()); + cellWriteHandlerContext.setOriginalValue(value); + cellWriteHandlerContext.setOriginalFieldClass(FieldUtils.getFieldClass(dataMap, variable, value)); + cellWriteHandlerContext.setExcelContentProperty(excelContentProperty); + cellWriteHandlerContext.setTargetCellDataType(CellDataTypeEnum.STRING); + + WriteCellData cellData = convert(cellWriteHandlerContext); + cellDataList.add(cellData); + + CellDataTypeEnum type = cellData.getType(); + if (type != null) { + switch (type) { + case STRING: + cellValueBuild.append(cellData.getStringValue()); + break; + case BOOLEAN: + cellValueBuild.append(cellData.getBooleanValue()); + break; + case NUMBER: + cellValueBuild.append(cellData.getNumberValue()); + break; + default: + break; + } + } + } + cellValueBuild.append(analysisCell.getPrepareDataList().get(index)); + cell.setCellValue(cellValueBuild.toString()); + cellWriteHandlerContext.setCellDataList(cellDataList); + if (CollectionUtils.isNotEmpty(cellDataList)) { + cellWriteHandlerContext.setFirstCellData(cellDataList.get(0)); + } + + // Restyle + if (fillConfig.getAutoStyle()) { + Optional.ofNullable(collectionFieldStyleCache.get(currentUniqueDataFlag)) + .map(collectionFieldStyleMap -> collectionFieldStyleMap.get(analysisCell)) + .ifPresent(cell::setCellStyle); + } + } + WriteHandlerUtils.afterCellDispose(cellWriteHandlerContext); + } + + // In the case of the fill line may be called many times + if (rowWriteHandlerContext.getRow() != null) { + WriteHandlerUtils.afterRowDispose(rowWriteHandlerContext); + } + } + + private Integer getRelativeRowIndex() { + Integer relativeRowIndex = relativeRowIndexMap.get(currentUniqueDataFlag); + if (relativeRowIndex == null) { + relativeRowIndex = 0; + } else { + relativeRowIndex++; + } + relativeRowIndexMap.put(currentUniqueDataFlag, relativeRowIndex); + return relativeRowIndex; + } + + private void createCell(AnalysisCell analysisCell, FillConfig fillConfig, + CellWriteHandlerContext cellWriteHandlerContext, RowWriteHandlerContext rowWriteHandlerContext) { + Sheet cachedSheet = writeContext.writeSheetHolder().getCachedSheet(); + if (WriteTemplateAnalysisCellTypeEnum.COMMON.equals(analysisCell.getCellType())) { + Row row = cachedSheet.getRow(analysisCell.getRowIndex()); + cellWriteHandlerContext.setRow(row); + Cell cell = row.getCell(analysisCell.getColumnIndex()); + cellWriteHandlerContext.setCell(cell); + rowWriteHandlerContext.setRow(row); + rowWriteHandlerContext.setRowIndex(analysisCell.getRowIndex()); + return; + } + Sheet sheet = writeContext.writeSheetHolder().getSheet(); + + Map collectionLastIndexMap = collectionLastIndexCache + .computeIfAbsent(currentUniqueDataFlag, key -> MapUtils.newHashMap()); + + boolean isOriginalCell = false; + Integer lastRowIndex; + Integer lastColumnIndex; + switch (fillConfig.getDirection()) { + case VERTICAL: + lastRowIndex = collectionLastIndexMap.get(analysisCell); + if (lastRowIndex == null) { + lastRowIndex = analysisCell.getRowIndex(); + collectionLastIndexMap.put(analysisCell, lastRowIndex); + isOriginalCell = true; + } else { + collectionLastIndexMap.put(analysisCell, ++lastRowIndex); + } + lastColumnIndex = analysisCell.getColumnIndex(); + break; + case HORIZONTAL: + lastRowIndex = analysisCell.getRowIndex(); + lastColumnIndex = collectionLastIndexMap.get(analysisCell); + if (lastColumnIndex == null) { + lastColumnIndex = analysisCell.getColumnIndex(); + collectionLastIndexMap.put(analysisCell, lastColumnIndex); + isOriginalCell = true; + } else { + collectionLastIndexMap.put(analysisCell, ++lastColumnIndex); + } + break; + default: + throw new ExcelGenerateException("The wrong direction."); + } + + Row row = createRowIfNecessary(sheet, cachedSheet, lastRowIndex, fillConfig, analysisCell, isOriginalCell, + rowWriteHandlerContext); + cellWriteHandlerContext.setRow(row); + + cellWriteHandlerContext.setRowIndex(lastRowIndex); + cellWriteHandlerContext.setColumnIndex(lastColumnIndex); + Cell cell = createCellIfNecessary(row, lastColumnIndex, cellWriteHandlerContext); + cellWriteHandlerContext.setCell(cell); + + if (isOriginalCell) { + Map collectionFieldStyleMap = collectionFieldStyleCache.computeIfAbsent( + currentUniqueDataFlag, key -> MapUtils.newHashMap()); + collectionFieldStyleMap.put(analysisCell, cell.getCellStyle()); + } + } + + private Cell createCellIfNecessary(Row row, Integer lastColumnIndex, + CellWriteHandlerContext cellWriteHandlerContext) { + Cell cell = row.getCell(lastColumnIndex); + if (cell != null) { + return cell; + } + WriteHandlerUtils.beforeCellCreate(cellWriteHandlerContext); + cell = row.createCell(lastColumnIndex); + cellWriteHandlerContext.setCell(cell); + + WriteHandlerUtils.afterCellCreate(cellWriteHandlerContext); + return cell; + } + + private Row createRowIfNecessary(Sheet sheet, Sheet cachedSheet, Integer lastRowIndex, FillConfig fillConfig, + AnalysisCell analysisCell, boolean isOriginalCell, RowWriteHandlerContext rowWriteHandlerContext) { + rowWriteHandlerContext.setRowIndex(lastRowIndex); + Row row = sheet.getRow(lastRowIndex); + if (row != null) { + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row); + rowWriteHandlerContext.setRow(row); + return row; + } + row = cachedSheet.getRow(lastRowIndex); + if (row == null) { + rowWriteHandlerContext.setRowIndex(lastRowIndex); + WriteHandlerUtils.beforeRowCreate(rowWriteHandlerContext); + + if (fillConfig.getForceNewRow()) { + row = cachedSheet.createRow(lastRowIndex); + } else { + // The last row of the middle disk inside empty rows, resulting in cachedSheet can not get inside. + // Will throw Attempting to write a row[" + rownum + "] " + "in the range [0," + this._sh + // .getLastRowNum() + "] that is already written to disk. + try { + row = sheet.createRow(lastRowIndex); + } catch (IllegalArgumentException ignore) { + row = cachedSheet.createRow(lastRowIndex); + } + } + rowWriteHandlerContext.setRow(row); + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row); + + WriteHandlerUtils.afterRowCreate(rowWriteHandlerContext); + } else { + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row); + rowWriteHandlerContext.setRow(row); + } + return row; + } + + private void checkRowHeight(AnalysisCell analysisCell, FillConfig fillConfig, boolean isOriginalCell, Row row) { + if (!analysisCell.getFirstRow() || !WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection())) { + return; + } + // fix https://github.com/alibaba/easyexcel/issues/1869 + if (isOriginalCell && PoiUtils.customHeight(row)) { + collectionRowHeightCache.put(currentUniqueDataFlag, row.getHeight()); + return; + } + if (fillConfig.getAutoStyle()) { + Short rowHeight = collectionRowHeightCache.get(currentUniqueDataFlag); + if (rowHeight != null) { + row.setHeight(rowHeight); + } + } + } + + private List readTemplateData(Map> analysisCache) { + List analysisCellList = analysisCache.get(currentUniqueDataFlag); + if (analysisCellList != null) { + return analysisCellList; + } + Sheet sheet = writeContext.writeSheetHolder().getCachedSheet(); + Map> firstRowCache = MapUtils.newHashMapWithExpectedSize(8); + for (int i = 0; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) { + continue; + } + for (int j = 0; j < row.getLastCellNum(); j++) { + Cell cell = row.getCell(j); + if (cell == null) { + continue; + } + String preparedData = prepareData(cell, i, j, firstRowCache); + // Prevent empty data from not being replaced + if (preparedData != null) { + cell.setCellValue(preparedData); + } + } + } + return analysisCache.get(currentUniqueDataFlag); + } + + /** + * To prepare data + * + * @param cell cell + * @param rowIndex row index + * @param columnIndex column index + * @param firstRowCache first row cache + * @return Returns the data that the cell needs to replace + */ + private String prepareData(Cell cell, int rowIndex, int columnIndex, + Map> firstRowCache) { + if (!CellType.STRING.equals(cell.getCellType())) { + return null; + } + String value = cell.getStringCellValue(); + if (StringUtils.isEmpty(value)) { + return null; + } + StringBuilder preparedData = new StringBuilder(); + AnalysisCell analysisCell = null; + + int startIndex = 0; + int length = value.length(); + int lastPrepareDataIndex = 0; + out: + while (startIndex < length) { + int prefixIndex = value.indexOf(FILL_PREFIX, startIndex); + if (prefixIndex < 0) { + break; + } + if (prefixIndex != 0) { + char prefixPrefixChar = value.charAt(prefixIndex - 1); + if (prefixPrefixChar == IGNORE_CHAR) { + startIndex = prefixIndex + 1; + continue; + } + } + int suffixIndex = -1; + while (suffixIndex == -1 && startIndex < length) { + suffixIndex = value.indexOf(FILL_SUFFIX, startIndex + 1); + if (suffixIndex < 0) { + break out; + } + startIndex = suffixIndex + 1; + char prefixSuffixChar = value.charAt(suffixIndex - 1); + if (prefixSuffixChar == IGNORE_CHAR) { + suffixIndex = -1; + } + } + if (analysisCell == null) { + analysisCell = initAnalysisCell(rowIndex, columnIndex); + } + String variable = value.substring(prefixIndex + 1, suffixIndex); + if (StringUtils.isEmpty(variable)) { + continue; + } + int collectPrefixIndex = variable.indexOf(COLLECTION_PREFIX); + if (collectPrefixIndex > -1) { + if (collectPrefixIndex != 0) { + analysisCell.setPrefix(variable.substring(0, collectPrefixIndex)); + } + variable = variable.substring(collectPrefixIndex + 1); + if (StringUtils.isEmpty(variable)) { + continue; + } + analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COLLECTION); + } + analysisCell.getVariableList().add(variable); + if (lastPrepareDataIndex == prefixIndex) { + analysisCell.getPrepareDataList().add(StringUtils.EMPTY); + // fix https://github.com/alibaba/easyexcel/issues/2035 + if (lastPrepareDataIndex != 0) { + analysisCell.setOnlyOneVariable(Boolean.FALSE); + } + } else { + String data = convertPrepareData(value.substring(lastPrepareDataIndex, prefixIndex)); + preparedData.append(data); + analysisCell.getPrepareDataList().add(data); + analysisCell.setOnlyOneVariable(Boolean.FALSE); + } + lastPrepareDataIndex = suffixIndex + 1; + } + // fix https://github.com/alibaba/easyexcel/issues/1552 + // When read template, XLSX data may be in `is` labels, and set the time set in `v` label, lead to can't set + // up successfully, so all data format to empty first. + if (analysisCell != null && CollectionUtils.isNotEmpty(analysisCell.getVariableList())) { + cell.setBlank(); + } + return dealAnalysisCell(analysisCell, value, rowIndex, lastPrepareDataIndex, length, firstRowCache, + preparedData); + } + + private String dealAnalysisCell(AnalysisCell analysisCell, String value, int rowIndex, int lastPrepareDataIndex, + int length, Map> firstRowCache, StringBuilder preparedData) { + if (analysisCell != null) { + if (lastPrepareDataIndex == length) { + analysisCell.getPrepareDataList().add(StringUtils.EMPTY); + } else { + analysisCell.getPrepareDataList().add(convertPrepareData(value.substring(lastPrepareDataIndex))); + analysisCell.setOnlyOneVariable(Boolean.FALSE); + } + UniqueDataFlagKey uniqueDataFlag = uniqueDataFlag(writeContext.writeSheetHolder(), + analysisCell.getPrefix()); + if (WriteTemplateAnalysisCellTypeEnum.COMMON.equals(analysisCell.getCellType())) { + List analysisCellList = templateAnalysisCache.computeIfAbsent(uniqueDataFlag, + key -> ListUtils.newArrayList()); + analysisCellList.add(analysisCell); + } else { + Set uniqueFirstRowCache = firstRowCache.computeIfAbsent(uniqueDataFlag, + key -> new HashSet<>()); + + if (!uniqueFirstRowCache.contains(rowIndex)) { + analysisCell.setFirstRow(Boolean.TRUE); + uniqueFirstRowCache.add(rowIndex); + } + + List collectionAnalysisCellList = templateCollectionAnalysisCache.computeIfAbsent( + uniqueDataFlag, key -> ListUtils.newArrayList()); + + collectionAnalysisCellList.add(analysisCell); + } + return preparedData.toString(); + } + return null; + } + + private AnalysisCell initAnalysisCell(Integer rowIndex, Integer columnIndex) { + AnalysisCell analysisCell = new AnalysisCell(); + analysisCell.setRowIndex(rowIndex); + analysisCell.setColumnIndex(columnIndex); + analysisCell.setOnlyOneVariable(Boolean.TRUE); + List variableList = ListUtils.newArrayList(); + analysisCell.setVariableList(variableList); + List prepareDataList = ListUtils.newArrayList(); + analysisCell.setPrepareDataList(prepareDataList); + analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COMMON); + analysisCell.setFirstRow(Boolean.FALSE); + return analysisCell; + } + + private String convertPrepareData(String prepareData) { + prepareData = prepareData.replaceAll(ESCAPE_FILL_PREFIX, FILL_PREFIX); + prepareData = prepareData.replaceAll(ESCAPE_FILL_SUFFIX, FILL_SUFFIX); + return prepareData; + } + + private UniqueDataFlagKey uniqueDataFlag(WriteSheetHolder writeSheetHolder, String wrapperName) { + return new UniqueDataFlagKey(writeSheetHolder.getSheetNo(), writeSheetHolder.getSheetName(), wrapperName); + } + + @Getter + @Setter + @EqualsAndHashCode + @AllArgsConstructor + public static class UniqueDataFlagKey { + private Integer sheetNo; + private String sheetName; + private String wrapperName; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractCellWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractCellWriteHandler.java new file mode 100644 index 0000000..9c8f475 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractCellWriteHandler.java @@ -0,0 +1,44 @@ +package ai.chat2db.excel.write.handler; + +import java.util.List; + +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; + +/** + * Abstract cell write handler + * + * @author Jiaju Zhuang + * @deprecated Please use it directly {@link CellWriteHandler} + **/ +@Deprecated +public abstract class AbstractCellWriteHandler implements CellWriteHandler { + + @Override + public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, + Head head, Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + WriteCellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + } + + @Override + public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + List> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractRowWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractRowWriteHandler.java new file mode 100644 index 0000000..ba0334b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractRowWriteHandler.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; + +import org.apache.poi.ss.usermodel.Row; + +/** + * Abstract row write handler + * + * @author Jiaju Zhuang + * @deprecated Please use it directly {@link RowWriteHandler} + **/ +@Deprecated +public abstract class AbstractRowWriteHandler implements RowWriteHandler { + @Override + public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, + Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractSheetWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractSheetWriteHandler.java new file mode 100644 index 0000000..0dc5f8a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractSheetWriteHandler.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * Abstract sheet write handler + * + * @author Jiaju Zhuang + * @deprecated Please use it directly {@link SheetWriteHandler} + **/ +@Deprecated +public abstract class AbstractSheetWriteHandler implements SheetWriteHandler { + @Override + public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractWorkbookWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractWorkbookWriteHandler.java new file mode 100644 index 0000000..9fcdb5d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/AbstractWorkbookWriteHandler.java @@ -0,0 +1,28 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * Abstract workbook write handler + * + * @author Jiaju Zhuang + * @deprecated Please use it directly {@link WorkbookWriteHandler} + **/ +@Deprecated +public abstract class AbstractWorkbookWriteHandler implements WorkbookWriteHandler { + + @Override + public void beforeWorkbookCreate() { + + } + + @Override + public void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) { + + } + + @Override + public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) { + + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/CellWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/CellWriteHandler.java new file mode 100644 index 0000000..e7b92f7 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/CellWriteHandler.java @@ -0,0 +1,118 @@ +package ai.chat2db.excel.write.handler; + +import java.util.List; + +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; + +/** + * intercepts handle cell creation + * + * @author Jiaju Zhuang + */ +public interface CellWriteHandler extends WriteHandler { + + /** + * Called before create the cell + * + * @param context + */ + default void beforeCellCreate(CellWriteHandlerContext context) { + beforeCellCreate(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getRow(), + context.getHeadData(), context.getColumnIndex(), context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called before create the cell + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param row + * @param head Nullable.It is null in the case of fill data and without head. + * @param columnIndex + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead It will always be false when fill data. + */ + default void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {} + + /** + * Called after the cell is created + * + * @param context + */ + default void afterCellCreate(CellWriteHandlerContext context) { + afterCellCreate(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getCell(), + context.getHeadData(), context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called after the cell is created + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param cell + * @param head Nullable.It is null in the case of fill data and without head. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead It will always be false when fill data. + */ + default void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, + Head head, Integer relativeRowIndex, Boolean isHead) {} + + /** + * Called after the cell data is converted + * + * @param context + */ + default void afterCellDataConverted(CellWriteHandlerContext context) { + WriteCellData writeCellData = CollectionUtils.isNotEmpty(context.getCellDataList()) ? context + .getCellDataList().get(0) : null; + afterCellDataConverted(context.getWriteSheetHolder(), context.getWriteTableHolder(), writeCellData, + context.getCell(), context.getHeadData(), context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called after the cell data is converted + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param cell + * @param head Nullable.It is null in the case of fill data and without head. + * @param cellData Nullable.It is null in the case of add header. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead It will always be false when fill data. + */ + default void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + WriteCellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {} + + /** + * Called after all operations on the cell have been completed + * + * @param context + */ + default void afterCellDispose(CellWriteHandlerContext context) { + afterCellDispose(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getCellDataList(), + context.getCell(), context.getHeadData(), context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called after all operations on the cell have been completed + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param cell + * @param head Nullable.It is null in the case of fill data and without head. + * @param cellDataList Nullable.It is null in the case of add header.There may be several when fill the data. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead It will always be false when fill data. + */ + default void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + List> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {} +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/DefaultWriteHandlerLoader.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/DefaultWriteHandlerLoader.java new file mode 100644 index 0000000..bf63e38 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/DefaultWriteHandlerLoader.java @@ -0,0 +1,60 @@ +package ai.chat2db.excel.write.handler; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.write.handler.impl.DefaultRowWriteHandler; +import ai.chat2db.excel.write.handler.impl.DimensionWorkbookWriteHandler; +import ai.chat2db.excel.write.handler.impl.FillStyleCellWriteHandler; +import ai.chat2db.excel.write.style.DefaultStyle; + +/** + * Load default handler + * + * @author Jiaju Zhuang + */ +public class DefaultWriteHandlerLoader { + + public static final List DEFAULT_WRITE_HANDLER_LIST = new ArrayList<>(); + + static { + DEFAULT_WRITE_HANDLER_LIST.add(new DimensionWorkbookWriteHandler()); + DEFAULT_WRITE_HANDLER_LIST.add(new DefaultRowWriteHandler()); + DEFAULT_WRITE_HANDLER_LIST.add(new FillStyleCellWriteHandler()); + } + + /** + * Load default handler + * + * @return + */ + public static List loadDefaultHandler(Boolean useDefaultStyle, ExcelTypeEnum excelType) { + List handlerList = new ArrayList<>(); + switch (excelType) { + case XLSX: + handlerList.add(new DimensionWorkbookWriteHandler()); + handlerList.add(new DefaultRowWriteHandler()); + handlerList.add(new FillStyleCellWriteHandler()); + if (useDefaultStyle) { + handlerList.add(new DefaultStyle()); + } + break; + case XLS: + handlerList.add(new DefaultRowWriteHandler()); + handlerList.add(new FillStyleCellWriteHandler()); + if (useDefaultStyle) { + handlerList.add(new DefaultStyle()); + } + break; + case CSV: + handlerList.add(new DefaultRowWriteHandler()); + handlerList.add(new FillStyleCellWriteHandler()); + break; + default: + break; + } + return handlerList; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/RowWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/RowWriteHandler.java new file mode 100644 index 0000000..81eb551 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/RowWriteHandler.java @@ -0,0 +1,83 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; + +import org.apache.poi.ss.usermodel.Row; + +/** + * intercepts handle row creation + * + * @author Jiaju Zhuang + */ +public interface RowWriteHandler extends WriteHandler { + + /** + * Called before create the row + * + * @param context + */ + default void beforeRowCreate(RowWriteHandlerContext context) { + beforeRowCreate(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getRowIndex(), + context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called before create the row + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param rowIndex + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. + */ + default void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, + Integer relativeRowIndex, Boolean isHead) {} + + /** + * Called after the row is created + * + * @param context + */ + default void afterRowCreate(RowWriteHandlerContext context) { + afterRowCreate(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getRow(), + context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called after the row is created + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param row + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. + */ + default void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) {} + + /** + * Called after all operations on the row have been completed. + * In the case of the fill , may be called many times. + * + * @param context + */ + default void afterRowDispose(RowWriteHandlerContext context) { + afterRowDispose(context.getWriteSheetHolder(), context.getWriteTableHolder(), context.getRow(), + context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Called after all operations on the row have been completed. + * In the case of the fill , may be called many times. + * + * @param writeSheetHolder + * @param writeTableHolder Nullable.It is null without using table writes. + * @param row + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. + */ + default void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) {} +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/SheetWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/SheetWriteHandler.java new file mode 100644 index 0000000..b81184b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/SheetWriteHandler.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.handler.context.SheetWriteHandlerContext; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * intercepts handle sheet creation + * + * @author Jiaju Zhuang + */ +public interface SheetWriteHandler extends WriteHandler { + + /** + * Called before create the sheet + * + * @param context + */ + default void beforeSheetCreate(SheetWriteHandlerContext context) { + beforeSheetCreate(context.getWriteWorkbookHolder(), context.getWriteSheetHolder()); + } + + /** + * Called before create the sheet + * + * @param writeWorkbookHolder + * @param writeSheetHolder + */ + default void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {} + + /** + * Called after the sheet is created + * + * @param context + */ + default void afterSheetCreate(SheetWriteHandlerContext context) { + afterSheetCreate(context.getWriteWorkbookHolder(), context.getWriteSheetHolder()); + } + + /** + * Called after the sheet is created + * + * @param writeWorkbookHolder + * @param writeSheetHolder + */ + default void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {} +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WorkbookWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WorkbookWriteHandler.java new file mode 100644 index 0000000..984a879 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WorkbookWriteHandler.java @@ -0,0 +1,58 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * intercepts handle Workbook creation + * + * @author Jiaju Zhuang + */ +public interface WorkbookWriteHandler extends WriteHandler { + + /** + * Called before create the workbook + * + * @param context + */ + default void beforeWorkbookCreate(WorkbookWriteHandlerContext context) { + beforeWorkbookCreate(); + } + + /** + * Called before create the workbook + */ + default void beforeWorkbookCreate() {} + + /** + * Called after the workbook is created + * + * @param context + */ + default void afterWorkbookCreate(WorkbookWriteHandlerContext context) { + afterWorkbookCreate(context.getWriteWorkbookHolder()); + } + + /** + * Called after the workbook is created + * + * @param writeWorkbookHolder + */ + default void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {} + + /** + * Called after all operations on the workbook have been completed + * + * @param context + */ + default void afterWorkbookDispose(WorkbookWriteHandlerContext context) { + afterWorkbookDispose(context.getWriteWorkbookHolder()); + } + + /** + * Called after all operations on the workbook have been completed + * + * @param writeWorkbookHolder + */ + default void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {} +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WriteHandler.java new file mode 100644 index 0000000..10f6f6f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/WriteHandler.java @@ -0,0 +1,10 @@ +package ai.chat2db.excel.write.handler; + +import ai.chat2db.excel.event.Handler; + +/** + * intercepts handle excel write + * + * @author Jiaju Zhuang + */ +public interface WriteHandler extends Handler {} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/CellHandlerExecutionChain.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/CellHandlerExecutionChain.java new file mode 100644 index 0000000..629808c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/CellHandlerExecutionChain.java @@ -0,0 +1,67 @@ +package ai.chat2db.excel.write.handler.chain; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Execute the cell handler chain + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellHandlerExecutionChain { + /** + * next chain + */ + private CellHandlerExecutionChain next; + /** + * handler + */ + private CellWriteHandler handler; + + public CellHandlerExecutionChain(CellWriteHandler handler) { + this.handler = handler; + } + + public void beforeCellCreate(CellWriteHandlerContext context) { + this.handler.beforeCellCreate(context); + if (this.next != null) { + this.next.beforeCellCreate(context); + } + } + + public void afterCellCreate(CellWriteHandlerContext context) { + this.handler.afterCellCreate(context); + if (this.next != null) { + this.next.afterCellCreate(context); + } + } + + public void afterCellDataConverted(CellWriteHandlerContext context) { + this.handler.afterCellDataConverted(context); + if (this.next != null) { + this.next.afterCellDataConverted(context); + } + } + + public void afterCellDispose(CellWriteHandlerContext context) { + this.handler.afterCellDispose(context); + if (this.next != null) { + this.next.afterCellDispose(context); + } + } + + public void addLast(CellWriteHandler handler) { + CellHandlerExecutionChain context = this; + while (context.next != null) { + context = context.next; + } + context.next = new CellHandlerExecutionChain(handler); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/RowHandlerExecutionChain.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/RowHandlerExecutionChain.java new file mode 100644 index 0000000..f9e5fbd --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/RowHandlerExecutionChain.java @@ -0,0 +1,60 @@ +package ai.chat2db.excel.write.handler.chain; + +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Execute the row handler chain + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class RowHandlerExecutionChain { + /** + * next chain + */ + private RowHandlerExecutionChain next; + /** + * handler + */ + private RowWriteHandler handler; + + public RowHandlerExecutionChain(RowWriteHandler handler) { + this.handler = handler; + } + + public void beforeRowCreate(RowWriteHandlerContext context) { + this.handler.beforeRowCreate(context); + if (this.next != null) { + this.next.beforeRowCreate(context); + } + } + + public void afterRowCreate(RowWriteHandlerContext context) { + this.handler.afterRowCreate(context); + if (this.next != null) { + this.next.afterRowCreate(context); + } + } + + public void afterRowDispose(RowWriteHandlerContext context) { + this.handler.afterRowDispose(context); + if (this.next != null) { + this.next.afterRowDispose(context); + } + } + + public void addLast(RowWriteHandler handler) { + RowHandlerExecutionChain context = this; + while (context.next != null) { + context = context.next; + } + context.next = new RowHandlerExecutionChain(handler); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/SheetHandlerExecutionChain.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/SheetHandlerExecutionChain.java new file mode 100644 index 0000000..b921710 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/SheetHandlerExecutionChain.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.write.handler.chain; + +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import ai.chat2db.excel.write.handler.context.SheetWriteHandlerContext; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Execute the sheet handler chain + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class SheetHandlerExecutionChain { + /** + * next chain + */ + private SheetHandlerExecutionChain next; + /** + * handler + */ + private SheetWriteHandler handler; + + public SheetHandlerExecutionChain(SheetWriteHandler handler) { + this.handler = handler; + } + + public void beforeSheetCreate(SheetWriteHandlerContext context) { + this.handler.beforeSheetCreate(context); + if (this.next != null) { + this.next.beforeSheetCreate(context); + } + } + + public void afterSheetCreate(SheetWriteHandlerContext context) { + this.handler.afterSheetCreate(context); + if (this.next != null) { + this.next.afterSheetCreate(context); + } + } + public void addLast(SheetWriteHandler handler) { + SheetHandlerExecutionChain context = this; + while (context.next != null) { + context = context.next; + } + context.next = new SheetHandlerExecutionChain(handler); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/WorkbookHandlerExecutionChain.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/WorkbookHandlerExecutionChain.java new file mode 100644 index 0000000..29e6c6b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/chain/WorkbookHandlerExecutionChain.java @@ -0,0 +1,61 @@ +package ai.chat2db.excel.write.handler.chain; + +import ai.chat2db.excel.write.handler.WorkbookWriteHandler; +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Execute the workbook handler chain + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class WorkbookHandlerExecutionChain { + /** + * next chain + */ + private WorkbookHandlerExecutionChain next; + + /** + * handler + */ + private WorkbookWriteHandler handler; + + public WorkbookHandlerExecutionChain(WorkbookWriteHandler handler) { + this.handler = handler; + } + + public void beforeWorkbookCreate(WorkbookWriteHandlerContext context) { + this.handler.beforeWorkbookCreate(context); + if (this.next != null) { + this.next.beforeWorkbookCreate(context); + } + } + + public void afterWorkbookCreate(WorkbookWriteHandlerContext context) { + this.handler.afterWorkbookCreate(context); + if (this.next != null) { + this.next.afterWorkbookCreate(context); + } + } + + public void afterWorkbookDispose(WorkbookWriteHandlerContext context) { + this.handler.afterWorkbookDispose(context); + if (this.next != null) { + this.next.afterWorkbookDispose(context); + } + } + + public void addLast(WorkbookWriteHandler handler) { + WorkbookHandlerExecutionChain context = this; + while (context.next != null) { + context = context.next; + } + context.next = new WorkbookHandlerExecutionChain(handler); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/CellWriteHandlerContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/CellWriteHandlerContext.java new file mode 100644 index 0000000..9992652 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/CellWriteHandlerContext.java @@ -0,0 +1,132 @@ +package ai.chat2db.excel.write.handler.context; + +import java.util.List; + +import ai.chat2db.excel.write.handler.impl.FillStyleCellWriteHandler; +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; + +/** + * cell context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellWriteHandlerContext { + /** + * write context + */ + private WriteContext writeContext; + /** + * workbook + */ + private WriteWorkbookHolder writeWorkbookHolder; + /** + * sheet + */ + private WriteSheetHolder writeSheetHolder; + /** + * table .Nullable.It is null without using table writes. + */ + private WriteTableHolder writeTableHolder; + /** + * row + */ + private Row row; + /** + * index + */ + private Integer rowIndex; + /** + * cell + */ + private Cell cell; + /** + * index + */ + private Integer columnIndex; + /** + * Nullable.It is null in the case of fill data. + */ + private Integer relativeRowIndex; + /** + * Nullable.It is null in the case of fill data. + */ + private Head headData; + /** + * Nullable.It is null in the case of add header.There may be several when fill the data. + */ + private List> cellDataList; + /** + * Nullable. + * It is null in the case of add header. + * In the case of write there must be not null. + * firstCellData == cellDataList.get(0) + */ + private WriteCellData firstCellData; + /** + * Nullable.It is null in the case of fill data. + */ + private Boolean head; + /** + * Field annotation configuration information. + */ + private ExcelContentProperty excelContentProperty; + + /** + * The value of the original + */ + private Object originalValue; + + /** + * The original field type + */ + private Class originalFieldClass; + + /** + * Target cell data type + */ + private CellDataTypeEnum targetCellDataType; + + /** + * Ignore the filling pattern and the {@code FillStyleCellWriteHandler} will not work. + * + * @see FillStyleCellWriteHandler + */ + private Boolean ignoreFillStyle; + + public CellWriteHandlerContext(WriteContext writeContext, + WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder, + WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Cell cell, Integer columnIndex, + Integer relativeRowIndex, Head headData, List> cellDataList, WriteCellData firstCellData, + Boolean head, ExcelContentProperty excelContentProperty) { + this.writeContext = writeContext; + this.writeWorkbookHolder = writeWorkbookHolder; + this.writeSheetHolder = writeSheetHolder; + this.writeTableHolder = writeTableHolder; + this.row = row; + this.rowIndex = rowIndex; + this.cell = cell; + this.columnIndex = columnIndex; + this.relativeRowIndex = relativeRowIndex; + this.headData = headData; + this.cellDataList = cellDataList; + this.firstCellData = firstCellData; + this.head = head; + this.excelContentProperty = excelContentProperty; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/RowWriteHandlerContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/RowWriteHandlerContext.java new file mode 100644 index 0000000..8f749a6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/RowWriteHandlerContext.java @@ -0,0 +1,56 @@ +package ai.chat2db.excel.write.handler.context; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.Row; + +/** + * row context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +public class RowWriteHandlerContext { + /** + * write context + */ + private WriteContext writeContext; + /** + * workbook + */ + private WriteWorkbookHolder writeWorkbookHolder; + /** + * sheet + */ + private WriteSheetHolder writeSheetHolder; + /** + * table .Nullable.It is null without using table writes. + */ + private WriteTableHolder writeTableHolder; + /** + * row index + */ + private Integer rowIndex; + /** + * row + */ + private Row row; + /** + * Nullable.It is null in the case of fill data. + */ + private Integer relativeRowIndex; + /** + * Nullable.It is null in the case of fill data. + */ + private Boolean head; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/SheetWriteHandlerContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/SheetWriteHandlerContext.java new file mode 100644 index 0000000..5cd61cf --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/SheetWriteHandlerContext.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.write.handler.context; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * sheet context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +public class SheetWriteHandlerContext { + /** + * write context + */ + private WriteContext writeContext; + /** + * workbook + */ + private WriteWorkbookHolder writeWorkbookHolder; + /** + * sheet + */ + private WriteSheetHolder writeSheetHolder; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/WorkbookWriteHandlerContext.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/WorkbookWriteHandlerContext.java new file mode 100644 index 0000000..b3cb640 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/context/WorkbookWriteHandlerContext.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.write.handler.context; + +import ai.chat2db.excel.context.WriteContext; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * workbook context + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +public class WorkbookWriteHandlerContext { + /** + * write context + */ + private WriteContext writeContext; + /** + * workbook + */ + private WriteWorkbookHolder writeWorkbookHolder; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DefaultRowWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DefaultRowWriteHandler.java new file mode 100644 index 0000000..3fb3257 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DefaultRowWriteHandler.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.write.handler.impl; + +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; + +import lombok.extern.slf4j.Slf4j; + +/** + * Default row handler. + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DefaultRowWriteHandler implements RowWriteHandler { + + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + context.getWriteSheetHolder().setLastRowIndex(context.getRowIndex()); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DimensionWorkbookWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DimensionWorkbookWriteHandler.java new file mode 100644 index 0000000..52a301f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/DimensionWorkbookWriteHandler.java @@ -0,0 +1,81 @@ +package ai.chat2db.excel.write.handler.impl; + +import java.lang.reflect.Field; +import java.util.Map; + +import ai.chat2db.excel.write.handler.WorkbookWriteHandler; +import ai.chat2db.excel.util.FieldUtils; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; + +/** + * Handle the problem of unable to write dimension. + * + * https://github.com/alibaba/easyexcel/issues/1282 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DimensionWorkbookWriteHandler implements WorkbookWriteHandler { + + private static final String XSSF_SHEET_MEMBER_VARIABLE_NAME = "_sh"; + private static final Field XSSF_SHEET_FIELD = FieldUtils.getField(SXSSFSheet.class, XSSF_SHEET_MEMBER_VARIABLE_NAME, + true); + + @Override + public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) { + if (writeWorkbookHolder == null || writeWorkbookHolder.getWorkbook() == null) { + return; + } + if (!(writeWorkbookHolder.getWorkbook() instanceof SXSSFWorkbook)) { + return; + } + + Map writeSheetHolderMap = writeWorkbookHolder.getHasBeenInitializedSheetIndexMap(); + if (MapUtils.isEmpty(writeSheetHolderMap)) { + return; + } + for (WriteSheetHolder writeSheetHolder : writeSheetHolderMap.values()) { + if (writeSheetHolder.getSheet() == null || !(writeSheetHolder.getSheet() instanceof SXSSFSheet)) { + continue; + } + SXSSFSheet sxssfSheet = ((SXSSFSheet)writeSheetHolder.getSheet()); + XSSFSheet xssfSheet; + try { + xssfSheet = (XSSFSheet)XSSF_SHEET_FIELD.get(sxssfSheet); + } catch (IllegalAccessException e) { + log.debug("Can not found _sh.", e); + continue; + } + if (xssfSheet == null) { + continue; + } + CTWorksheet ctWorksheet = xssfSheet.getCTWorksheet(); + if (ctWorksheet == null) { + continue; + } + int headSize = 0; + if (MapUtils.isNotEmpty(writeSheetHolder.getExcelWriteHeadProperty().getHeadMap())) { + headSize = writeSheetHolder.getExcelWriteHeadProperty().getHeadMap().size(); + if (headSize > 0) { + headSize--; + } + } + Integer lastRowIndex = writeSheetHolder.getLastRowIndex(); + if (lastRowIndex == null) { + lastRowIndex = 0; + } + + ctWorksheet.getDimension().setRef( + "A1:" + CellReference.convertNumToColString(headSize) + (lastRowIndex + 1)); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/FillStyleCellWriteHandler.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/FillStyleCellWriteHandler.java new file mode 100644 index 0000000..35d85c4 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/handler/impl/FillStyleCellWriteHandler.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.write.handler.impl; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.constant.OrderConstant; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.CellStyle; + +/** + * fill cell style. + * + * @author Jiaju Zhuang + */ +@Slf4j +public class FillStyleCellWriteHandler implements CellWriteHandler { + + @Override + public int order() { + return OrderConstant.FILL_STYLE; + } + + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + if (BooleanUtils.isTrue(context.getIgnoreFillStyle())) { + return; + } + + WriteCellData cellData = context.getFirstCellData(); + if (cellData == null) { + return; + } + WriteCellStyle writeCellStyle = cellData.getWriteCellStyle(); + CellStyle originCellStyle = cellData.getOriginCellStyle(); + if (writeCellStyle == null && originCellStyle == null) { + return; + } + WriteWorkbookHolder writeWorkbookHolder = context.getWriteWorkbookHolder(); + context.getCell().setCellStyle(writeWorkbookHolder.createCellStyle(writeCellStyle, originCellStyle)); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/AbstractMergeStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/AbstractMergeStrategy.java new file mode 100644 index 0000000..12a1f62 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/AbstractMergeStrategy.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.write.merge; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.metadata.Head; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * Merge strategy + * + * @author Jiaju Zhuang + */ +public abstract class AbstractMergeStrategy implements CellWriteHandler { + + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + if (context.getHead()) { + return; + } + merge(context.getWriteSheetHolder().getSheet(), context.getCell(), context.getHeadData(), + context.getRelativeRowIndex()); + } + + /** + * merge + * + * @param sheet + * @param cell + * @param head + * @param relativeRowIndex + */ + protected abstract void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/LoopMergeStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/LoopMergeStrategy.java new file mode 100644 index 0000000..bfc6d24 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/LoopMergeStrategy.java @@ -0,0 +1,67 @@ +package ai.chat2db.excel.write.merge; + +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.metadata.property.LoopMergeProperty; + +import org.apache.poi.ss.util.CellRangeAddress; + +/** + * The regions of the loop merge + * + * @author Jiaju Zhuang + */ +public class LoopMergeStrategy implements RowWriteHandler { + /** + * Each row + */ + private final int eachRow; + /** + * Extend column + */ + private final int columnExtend; + /** + * The number of the current column + */ + private final int columnIndex; + + public LoopMergeStrategy(int eachRow, int columnIndex) { + this(eachRow, 1, columnIndex); + } + + public LoopMergeStrategy(int eachRow, int columnExtend, int columnIndex) { + if (eachRow < 1) { + throw new IllegalArgumentException("EachRows must be greater than 1"); + } + if (columnExtend < 1) { + throw new IllegalArgumentException("ColumnExtend must be greater than 1"); + } + if (columnExtend == 1 && eachRow == 1) { + throw new IllegalArgumentException("ColumnExtend or eachRows must be greater than 1"); + } + if (columnIndex < 0) { + throw new IllegalArgumentException("ColumnIndex must be greater than 0"); + } + this.eachRow = eachRow; + this.columnExtend = columnExtend; + this.columnIndex = columnIndex; + } + + public LoopMergeStrategy(LoopMergeProperty loopMergeProperty, Integer columnIndex) { + this(loopMergeProperty.getEachRow(), loopMergeProperty.getColumnExtend(), columnIndex); + } + + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + if (context.getHead() || context.getRelativeRowIndex() == null) { + return; + } + if (context.getRelativeRowIndex() % eachRow == 0) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(context.getRowIndex(), + context.getRowIndex() + eachRow - 1, + columnIndex, columnIndex + columnExtend - 1); + context.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(cellRangeAddress); + } + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/OnceAbsoluteMergeStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/OnceAbsoluteMergeStrategy.java new file mode 100644 index 0000000..2f2adab --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/merge/OnceAbsoluteMergeStrategy.java @@ -0,0 +1,54 @@ +package ai.chat2db.excel.write.merge; + +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import org.apache.poi.ss.util.CellRangeAddress; + +import ai.chat2db.excel.metadata.property.OnceAbsoluteMergeProperty; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +/** + * It only merges once when create cell(firstRowIndex,lastRowIndex) + * + * @author Jiaju Zhuang + */ +public class OnceAbsoluteMergeStrategy implements SheetWriteHandler { + /** + * First row + */ + private final int firstRowIndex; + /** + * Last row + */ + private final int lastRowIndex; + /** + * First column + */ + private final int firstColumnIndex; + /** + * Last row + */ + private final int lastColumnIndex; + + public OnceAbsoluteMergeStrategy(int firstRowIndex, int lastRowIndex, int firstColumnIndex, int lastColumnIndex) { + if (firstRowIndex < 0 || lastRowIndex < 0 || firstColumnIndex < 0 || lastColumnIndex < 0) { + throw new IllegalArgumentException("All parameters must be greater than 0"); + } + this.firstRowIndex = firstRowIndex; + this.lastRowIndex = lastRowIndex; + this.firstColumnIndex = firstColumnIndex; + this.lastColumnIndex = lastColumnIndex; + } + + public OnceAbsoluteMergeStrategy(OnceAbsoluteMergeProperty onceAbsoluteMergeProperty) { + this(onceAbsoluteMergeProperty.getFirstRowIndex(), onceAbsoluteMergeProperty.getLastRowIndex(), + onceAbsoluteMergeProperty.getFirstColumnIndex(), onceAbsoluteMergeProperty.getLastColumnIndex()); + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + CellRangeAddress cellRangeAddress = + new CellRangeAddress(firstRowIndex, lastRowIndex, firstColumnIndex, lastColumnIndex); + writeSheetHolder.getSheet().addMergedRegionUnsafe(cellRangeAddress); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/CollectionRowData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/CollectionRowData.java new file mode 100644 index 0000000..cc7e4b6 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/CollectionRowData.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.write.metadata; + +import java.util.Collection; + +/** + * A collection row of data. + * + * @author Jiaju Zhuang + */ +public class CollectionRowData implements RowData { + + private final Object[] array; + + public CollectionRowData(Collection collection) { + this.array = collection.toArray(); + } + + @Override + public Object get(int index) { + return array[index]; + } + + @Override + public int size() { + return array.length; + } + + @Override + public boolean isEmpty() { + return array.length == 0; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/MapRowData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/MapRowData.java new file mode 100644 index 0000000..2259ae3 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/MapRowData.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.write.metadata; + +import java.util.Map; + +/** + * A map row of data. + * + * @author Jiaju Zhuang + */ +public class MapRowData implements RowData { + + private final Map map; + + public MapRowData(Map map) { + this.map = map; + } + + @Override + public Object get(int index) { + return map.get(index); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/RowData.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/RowData.java new file mode 100644 index 0000000..4b583b2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/RowData.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.write.metadata; + +/** + * A row of data. + * + * @author Jiaju Zhuang + */ +public interface RowData { + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + * @param index + * @return data + */ + Object get(int index); + + /** + * Returns the number of elements in this collection. If this collection + * contains more than Integer.MAX_VALUE elements, returns + * Integer.MAX_VALUE. + * + * @return the number of elements in this collection + */ + int size(); + + /** + * Returns true if this collection contains no elements. + * + * @return true if this collection contains no elements + */ + boolean isEmpty(); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteBasicParameter.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteBasicParameter.java new file mode 100644 index 0000000..7c93a30 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteBasicParameter.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.write.metadata; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import ai.chat2db.excel.metadata.BasicParameter; +import ai.chat2db.excel.write.handler.WriteHandler; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Write basic parameter + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class WriteBasicParameter extends BasicParameter { + /** + * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. + */ + private Integer relativeHeadRowIndex; + /** + * Need Head + */ + private Boolean needHead; + /** + * Custom type handler override the default + */ + private List customWriteHandlerList = new ArrayList(); + /** + * Use the default style.Default is true. + */ + private Boolean useDefaultStyle; + /** + * Whether to automatically merge headers.Default is true. + */ + private Boolean automaticMergeHead; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnIndexes; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnFieldNames; + /** + * Only output the custom columns. + */ + private Collection includeColumnIndexes; + /** + * Only output the custom columns. + */ + private Collection includeColumnFieldNames; + + /** + * Data will be order by {@link #includeColumnFieldNames} or {@link #includeColumnIndexes}. + * + * default is false. + */ + private Boolean orderByIncludeColumn; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteSheet.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteSheet.java new file mode 100644 index 0000000..3b5236c --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteSheet.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.write.metadata; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Write sheet + * + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteSheet extends WriteBasicParameter { + /** + * Starting from 0 + */ + private Integer sheetNo; + /** + * sheet name + */ + private String sheetName; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteTable.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteTable.java new file mode 100644 index 0000000..b023efb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteTable.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.write.metadata; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * table + * + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteTable extends WriteBasicParameter { + /** + * Starting from 0 + */ + private Integer tableNo; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteWorkbook.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteWorkbook.java new file mode 100644 index 0000000..088f617 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/WriteWorkbook.java @@ -0,0 +1,89 @@ +package ai.chat2db.excel.write.metadata; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Workbook + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class WriteWorkbook extends WriteBasicParameter { + /** + * Excel type.The default is xlsx + */ + private ExcelTypeEnum excelType; + /** + * Final output file + *

+ * If 'outputStream' and 'file' all not empty, file first + */ + private File file; + /** + * Final output stream + *

+ * If 'outputStream' and 'file' all not empty, file first + */ + private OutputStream outputStream; + /** + * charset. + * Only work on the CSV file + */ + private Charset charset; + + /** + * Set the encoding prefix in the csv file, otherwise the office may open garbled characters. + * Default true. + */ + private Boolean withBom; + + /** + * Template input stream + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private InputStream templateInputStream; + + /** + * Template file. + * This file is read into memory, excessive cases can lead to OOM. + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private File templateFile; + /** + * Default true. + */ + private Boolean autoCloseStream; + /** + * Mandatory use 'inputStream' .Default is false + */ + private Boolean mandatoryUseInputStream; + /** + * Whether the encryption + *

+ * WARRING:Encryption is when the entire file is read into memory, so it is very memory intensive. + */ + private String password; + /** + * Write excel in memory. Default false, the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/AnalysisCell.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/AnalysisCell.java new file mode 100644 index 0000000..afd2666 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/AnalysisCell.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.write.metadata.fill; + +import java.util.List; + +import ai.chat2db.excel.enums.WriteTemplateAnalysisCellTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Read the cells of the template while populating the data. + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class AnalysisCell { + private int columnIndex; + private int rowIndex; + private List variableList; + private List prepareDataList; + private Boolean onlyOneVariable; + private WriteTemplateAnalysisCellTypeEnum cellType; + private String prefix; + private Boolean firstRow; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AnalysisCell that = (AnalysisCell)o; + if (columnIndex != that.columnIndex) { + return false; + } + return rowIndex == that.rowIndex; + } + + @Override + public int hashCode() { + int result = columnIndex; + result = 31 * result + rowIndex; + return result; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillConfig.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillConfig.java new file mode 100644 index 0000000..6acf54b --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillConfig.java @@ -0,0 +1,57 @@ +package ai.chat2db.excel.write.metadata.fill; + +import ai.chat2db.excel.enums.WriteDirectionEnum; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Fill config + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FillConfig { + private WriteDirectionEnum direction; + /** + * Create a new row each time you use the list parameter.The default create if necessary. + *

+ * Warnning:If you use forceNewRow set true, will not be able to use asynchronous write file, simply + * say the whole file will be stored in memory. + */ + private Boolean forceNewRow; + + /** + * Automatically inherit style + * + * default true. + */ + private Boolean autoStyle; + + private boolean hasInit; + + public void init() { + if (hasInit) { + return; + } + if (direction == null) { + direction = WriteDirectionEnum.VERTICAL; + } + if (forceNewRow == null) { + forceNewRow = Boolean.FALSE; + } + if (autoStyle == null) { + autoStyle = Boolean.TRUE; + } + hasInit = true; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillWrapper.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillWrapper.java new file mode 100644 index 0000000..7dbcdfe --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/fill/FillWrapper.java @@ -0,0 +1,44 @@ +package ai.chat2db.excel.write.metadata.fill; + +import java.util.Collection; + +/** + * Multiple lists are supported when packing + * + * @author Jiaju Zhuang + **/ +public class FillWrapper { + /** + * The collection prefix that needs to be filled. + */ + private String name; + /** + * Data that needs to be filled. + */ + private Collection collectionData; + + public FillWrapper(Collection collectionData) { + this.collectionData = collectionData; + } + + public FillWrapper(String name, Collection collectionData) { + this.name = name; + this.collectionData = collectionData; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Collection getCollectionData() { + return collectionData; + } + + public void setCollectionData(Collection collectionData) { + this.collectionData = collectionData; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/AbstractWriteHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/AbstractWriteHolder.java new file mode 100644 index 0000000..1eb1f6f --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/AbstractWriteHolder.java @@ -0,0 +1,525 @@ +package ai.chat2db.excel.write.metadata.holder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import ai.chat2db.excel.constant.OrderConstant; +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ConverterKeyBuild; +import ai.chat2db.excel.converters.DefaultConverterLoader; +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.event.NotRepeatExecutor; +import ai.chat2db.excel.metadata.AbstractHolder; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.metadata.property.LoopMergeProperty; +import ai.chat2db.excel.metadata.property.OnceAbsoluteMergeProperty; +import ai.chat2db.excel.metadata.property.RowHeightProperty; +import ai.chat2db.excel.write.handler.chain.CellHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.RowHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.SheetHandlerExecutionChain; +import ai.chat2db.excel.write.handler.chain.WorkbookHandlerExecutionChain; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.merge.LoopMergeStrategy; +import ai.chat2db.excel.write.merge.OnceAbsoluteMergeStrategy; +import ai.chat2db.excel.write.metadata.WriteBasicParameter; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.property.ExcelWriteHeadProperty; +import ai.chat2db.excel.write.style.AbstractVerticalCellStyleStrategy; +import ai.chat2db.excel.write.style.column.AbstractHeadColumnWidthStyleStrategy; +import ai.chat2db.excel.write.style.row.SimpleRowHeightStyleStrategy; +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.DefaultWriteHandlerLoader; +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import ai.chat2db.excel.write.handler.WorkbookWriteHandler; +import ai.chat2db.excel.write.handler.WriteHandler; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; + +/** + * Write holder configuration + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public abstract class AbstractWriteHolder extends AbstractHolder implements WriteHolder { + /** + * Need Head + */ + private Boolean needHead; + /** + * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. + */ + private Integer relativeHeadRowIndex; + /** + * Excel head property + */ + private ExcelWriteHeadProperty excelWriteHeadProperty; + /** + * Use the default style.Default is true. + */ + private Boolean useDefaultStyle; + /** + * Whether to automatically merge headers.Default is true. + */ + private Boolean automaticMergeHead; + + /** + * Ignore the custom columns. + */ + private Collection excludeColumnIndexes; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnFieldNames; + /** + * Only output the custom columns. + */ + private Collection includeColumnIndexes; + /** + * Only output the custom columns. + */ + private Collection includeColumnFieldNames; + + /** + * Data will be order by {@link #includeColumnFieldNames} or {@link #includeColumnIndexes}. + * + * default is false. + */ + private Boolean orderByIncludeColumn; + + /** + * Write handler + */ + private List writeHandlerList; + + /** + * Execute the workbook handler chain + * Created in the sheet in the workbook interceptors will not be executed because the workbook to + * create an event long past. So when initializing sheet, supplementary workbook event. + */ + public WorkbookHandlerExecutionChain ownWorkbookHandlerExecutionChain; + /** + * Execute the sheet handler chain + * Created in the sheet in the workbook interceptors will not be executed because the workbook to + * create an event long past. So when initializing sheet, supplementary workbook event. + */ + public SheetHandlerExecutionChain ownSheetHandlerExecutionChain; + + /** + * Execute the workbook handler chain + */ + public WorkbookHandlerExecutionChain workbookHandlerExecutionChain; + /** + * Execute the sheet handler chain + */ + public SheetHandlerExecutionChain sheetHandlerExecutionChain; + + /** + * Execute the row handler chain + */ + public RowHandlerExecutionChain rowHandlerExecutionChain; + + /** + * Execute the cell handler chain + */ + public CellHandlerExecutionChain cellHandlerExecutionChain; + + public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWriteHolder parentAbstractWriteHolder) { + super(writeBasicParameter, parentAbstractWriteHolder); + + if (writeBasicParameter.getUseScientificFormat() != null) { + throw new UnsupportedOperationException("Currently does not support setting useScientificFormat."); + } + + if (writeBasicParameter.getNeedHead() == null) { + if (parentAbstractWriteHolder == null) { + this.needHead = Boolean.TRUE; + } else { + this.needHead = parentAbstractWriteHolder.getNeedHead(); + } + } else { + this.needHead = writeBasicParameter.getNeedHead(); + } + + if (writeBasicParameter.getRelativeHeadRowIndex() == null) { + if (parentAbstractWriteHolder == null) { + this.relativeHeadRowIndex = 0; + } else { + this.relativeHeadRowIndex = parentAbstractWriteHolder.getRelativeHeadRowIndex(); + } + } else { + this.relativeHeadRowIndex = writeBasicParameter.getRelativeHeadRowIndex(); + } + + if (writeBasicParameter.getUseDefaultStyle() == null) { + if (parentAbstractWriteHolder == null) { + this.useDefaultStyle = Boolean.TRUE; + } else { + this.useDefaultStyle = parentAbstractWriteHolder.getUseDefaultStyle(); + } + } else { + this.useDefaultStyle = writeBasicParameter.getUseDefaultStyle(); + } + + if (writeBasicParameter.getAutomaticMergeHead() == null) { + if (parentAbstractWriteHolder == null) { + this.automaticMergeHead = Boolean.TRUE; + } else { + this.automaticMergeHead = parentAbstractWriteHolder.getAutomaticMergeHead(); + } + } else { + this.automaticMergeHead = writeBasicParameter.getAutomaticMergeHead(); + } + + if (writeBasicParameter.getExcludeColumnFieldNames() == null && parentAbstractWriteHolder != null) { + this.excludeColumnFieldNames = parentAbstractWriteHolder.getExcludeColumnFieldNames(); + } else { + this.excludeColumnFieldNames = writeBasicParameter.getExcludeColumnFieldNames(); + } + if (writeBasicParameter.getExcludeColumnIndexes() == null && parentAbstractWriteHolder != null) { + this.excludeColumnIndexes = parentAbstractWriteHolder.getExcludeColumnIndexes(); + } else { + this.excludeColumnIndexes = writeBasicParameter.getExcludeColumnIndexes(); + } + if (writeBasicParameter.getIncludeColumnFieldNames() == null && parentAbstractWriteHolder != null) { + this.includeColumnFieldNames = parentAbstractWriteHolder.getIncludeColumnFieldNames(); + } else { + this.includeColumnFieldNames = writeBasicParameter.getIncludeColumnFieldNames(); + } + + if (writeBasicParameter.getOrderByIncludeColumn() == null) { + if (parentAbstractWriteHolder == null) { + this.orderByIncludeColumn = Boolean.FALSE; + } else { + this.orderByIncludeColumn = parentAbstractWriteHolder.getOrderByIncludeColumn(); + } + } else { + this.orderByIncludeColumn = writeBasicParameter.getOrderByIncludeColumn(); + } + + if (writeBasicParameter.getIncludeColumnIndexes() == null && parentAbstractWriteHolder != null) { + this.includeColumnIndexes = parentAbstractWriteHolder.getIncludeColumnIndexes(); + } else { + this.includeColumnIndexes = writeBasicParameter.getIncludeColumnIndexes(); + } + + // Initialization property + this.excelWriteHeadProperty = new ExcelWriteHeadProperty(this, getClazz(), getHead()); + + // Set converterMap + if (parentAbstractWriteHolder == null) { + setConverterMap(DefaultConverterLoader.loadDefaultWriteConverter()); + } else { + setConverterMap(new HashMap<>(parentAbstractWriteHolder.getConverterMap())); + } + if (writeBasicParameter.getCustomConverterList() != null + && !writeBasicParameter.getCustomConverterList().isEmpty()) { + for (Converter converter : writeBasicParameter.getCustomConverterList()) { + getConverterMap().put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); + } + } + } + + protected void initHandler(WriteBasicParameter writeBasicParameter, AbstractWriteHolder parentAbstractWriteHolder) { + // Set writeHandlerMap + List handlerList = new ArrayList<>(); + + // Initialization Annotation + initAnnotationConfig(handlerList, writeBasicParameter); + + if (writeBasicParameter.getCustomWriteHandlerList() != null + && !writeBasicParameter.getCustomWriteHandlerList().isEmpty()) { + handlerList.addAll(writeBasicParameter.getCustomWriteHandlerList()); + } + sortAndClearUpHandler(handlerList, true); + + if (parentAbstractWriteHolder != null) { + if (CollectionUtils.isNotEmpty(parentAbstractWriteHolder.getWriteHandlerList())) { + handlerList.addAll(parentAbstractWriteHolder.getWriteHandlerList()); + } + } else { + if (this instanceof WriteWorkbookHolder) { + handlerList.addAll(DefaultWriteHandlerLoader.loadDefaultHandler(useDefaultStyle, + ((WriteWorkbookHolder)this).getExcelType())); + } + } + sortAndClearUpHandler(handlerList, false); + } + + protected void initAnnotationConfig(List handlerList, WriteBasicParameter writeBasicParameter) { + if (!HeadKindEnum.CLASS.equals(getExcelWriteHeadProperty().getHeadKind())) { + return; + } + if (writeBasicParameter.getClazz() == null) { + return; + } + Map headMap = getExcelWriteHeadProperty().getHeadMap(); + boolean hasColumnWidth = false; + + for (Head head : headMap.values()) { + if (head.getColumnWidthProperty() != null) { + hasColumnWidth = true; + } + dealLoopMerge(handlerList, head); + } + + if (hasColumnWidth) { + dealColumnWidth(handlerList); + } + + dealStyle(handlerList); + dealRowHigh(handlerList); + dealOnceAbsoluteMerge(handlerList); + } + + private void dealStyle(List handlerList) { + WriteHandler styleStrategy = new AbstractVerticalCellStyleStrategy() { + @Override + public int order() { + return OrderConstant.ANNOTATION_DEFINE_STYLE; + } + + @Override + protected WriteCellStyle headCellStyle(CellWriteHandlerContext context) { + Head head = context.getHeadData(); + if (head == null) { + return null; + } + return WriteCellStyle.build(head.getHeadStyleProperty(), head.getHeadFontProperty()); + } + + @Override + protected WriteCellStyle contentCellStyle(CellWriteHandlerContext context) { + ExcelContentProperty excelContentProperty = context.getExcelContentProperty(); + return WriteCellStyle.build(excelContentProperty.getContentStyleProperty(), + excelContentProperty.getContentFontProperty()); + } + }; + handlerList.add(styleStrategy); + } + + private void dealLoopMerge(List handlerList, Head head) { + LoopMergeProperty loopMergeProperty = head.getLoopMergeProperty(); + if (loopMergeProperty == null) { + return; + } + handlerList.add(new LoopMergeStrategy(loopMergeProperty, head.getColumnIndex())); + } + + private void dealOnceAbsoluteMerge(List handlerList) { + OnceAbsoluteMergeProperty onceAbsoluteMergeProperty = + getExcelWriteHeadProperty().getOnceAbsoluteMergeProperty(); + if (onceAbsoluteMergeProperty == null) { + return; + } + handlerList.add(new OnceAbsoluteMergeStrategy(onceAbsoluteMergeProperty)); + } + + private void dealRowHigh(List handlerList) { + RowHeightProperty headRowHeightProperty = getExcelWriteHeadProperty().getHeadRowHeightProperty(); + RowHeightProperty contentRowHeightProperty = getExcelWriteHeadProperty().getContentRowHeightProperty(); + if (headRowHeightProperty == null && contentRowHeightProperty == null) { + return; + } + Short headRowHeight = null; + if (headRowHeightProperty != null) { + headRowHeight = headRowHeightProperty.getHeight(); + } + Short contentRowHeight = null; + if (contentRowHeightProperty != null) { + contentRowHeight = contentRowHeightProperty.getHeight(); + } + handlerList.add(new SimpleRowHeightStyleStrategy(headRowHeight, contentRowHeight)); + } + + private void dealColumnWidth(List handlerList) { + WriteHandler columnWidthStyleStrategy = new AbstractHeadColumnWidthStyleStrategy() { + @Override + protected Integer columnWidth(Head head, Integer columnIndex) { + if (head == null) { + return null; + } + if (head.getColumnWidthProperty() != null) { + return head.getColumnWidthProperty().getWidth(); + } + return null; + } + }; + handlerList.add(columnWidthStyleStrategy); + } + + protected void sortAndClearUpHandler(List handlerList, boolean runOwn) { + // sort + Map> orderExcelWriteHandlerMap = new TreeMap<>(); + for (WriteHandler handler : handlerList) { + int order = handler.order(); + if (orderExcelWriteHandlerMap.containsKey(order)) { + orderExcelWriteHandlerMap.get(order).add(handler); + } else { + List tempHandlerList = new ArrayList<>(); + tempHandlerList.add(handler); + orderExcelWriteHandlerMap.put(order, tempHandlerList); + } + } + // clean up + Set alreadyExistedHandlerSet = new HashSet<>(); + List cleanUpHandlerList = new ArrayList<>(); + for (Map.Entry> entry : orderExcelWriteHandlerMap.entrySet()) { + for (WriteHandler handler : entry.getValue()) { + if (handler instanceof NotRepeatExecutor) { + String uniqueValue = ((NotRepeatExecutor)handler).uniqueValue(); + if (alreadyExistedHandlerSet.contains(uniqueValue)) { + continue; + } + alreadyExistedHandlerSet.add(uniqueValue); + } + cleanUpHandlerList.add(handler); + } + } + + // build chain + if (!runOwn) { + this.writeHandlerList = new ArrayList<>(); + } + for (WriteHandler writeHandler : cleanUpHandlerList) { + buildChain(writeHandler, runOwn); + } + } + + protected void buildChain(WriteHandler writeHandler, boolean runOwn) { + if (writeHandler instanceof CellWriteHandler) { + if (!runOwn) { + if (cellHandlerExecutionChain == null) { + cellHandlerExecutionChain = new CellHandlerExecutionChain((CellWriteHandler)writeHandler); + } else { + cellHandlerExecutionChain.addLast((CellWriteHandler)writeHandler); + } + } + } + if (writeHandler instanceof RowWriteHandler) { + if (!runOwn) { + if (rowHandlerExecutionChain == null) { + rowHandlerExecutionChain = new RowHandlerExecutionChain((RowWriteHandler)writeHandler); + } else { + rowHandlerExecutionChain.addLast((RowWriteHandler)writeHandler); + } + } + } + if (writeHandler instanceof SheetWriteHandler) { + if (!runOwn) { + if (sheetHandlerExecutionChain == null) { + sheetHandlerExecutionChain = new SheetHandlerExecutionChain((SheetWriteHandler)writeHandler); + } else { + sheetHandlerExecutionChain.addLast((SheetWriteHandler)writeHandler); + } + } else { + if (ownSheetHandlerExecutionChain == null) { + ownSheetHandlerExecutionChain = new SheetHandlerExecutionChain((SheetWriteHandler)writeHandler); + } else { + ownSheetHandlerExecutionChain.addLast((SheetWriteHandler)writeHandler); + } + } + } + if (writeHandler instanceof WorkbookWriteHandler) { + if (!runOwn) { + if (workbookHandlerExecutionChain == null) { + workbookHandlerExecutionChain = new WorkbookHandlerExecutionChain( + (WorkbookWriteHandler)writeHandler); + } else { + workbookHandlerExecutionChain.addLast((WorkbookWriteHandler)writeHandler); + } + } else { + if (ownWorkbookHandlerExecutionChain == null) { + ownWorkbookHandlerExecutionChain = new WorkbookHandlerExecutionChain( + (WorkbookWriteHandler)writeHandler); + } else { + ownWorkbookHandlerExecutionChain.addLast((WorkbookWriteHandler)writeHandler); + } + } + } + if (!runOwn) { + this.writeHandlerList.add(writeHandler); + } + } + + @Override + public boolean ignore(String fieldName, Integer columnIndex) { + if (fieldName != null) { + if (includeColumnFieldNames != null && !includeColumnFieldNames.contains(fieldName)) { + return true; + } + if (excludeColumnFieldNames != null && excludeColumnFieldNames.contains(fieldName)) { + return true; + } + } + if (columnIndex != null) { + if (includeColumnIndexes != null && !includeColumnIndexes.contains(columnIndex)) { + return true; + } + if (excludeColumnIndexes != null && excludeColumnIndexes.contains(columnIndex)) { + return true; + } + } + return false; + } + + @Override + public ExcelWriteHeadProperty excelWriteHeadProperty() { + return getExcelWriteHeadProperty(); + } + + @Override + public boolean needHead() { + return getNeedHead(); + } + + @Override + public int relativeHeadRowIndex() { + return getRelativeHeadRowIndex(); + } + + @Override + public boolean automaticMergeHead() { + return getAutomaticMergeHead(); + } + + @Override + public boolean orderByIncludeColumn() { + return getOrderByIncludeColumn(); + } + + @Override + public Collection includeColumnIndexes() { + return getIncludeColumnIndexes(); + } + + @Override + public Collection includeColumnFieldNames() { + return getIncludeColumnFieldNames(); + } + + @Override + public Collection excludeColumnIndexes() { + return getExcludeColumnIndexes(); + } + + @Override + public Collection excludeColumnFieldNames() { + return getExcludeColumnFieldNames(); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteHolder.java new file mode 100644 index 0000000..1698b53 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteHolder.java @@ -0,0 +1,88 @@ +package ai.chat2db.excel.write.metadata.holder; + +import java.util.Collection; + +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.write.property.ExcelWriteHeadProperty; + +/** + * Get the corresponding Holder + * + * @author Jiaju Zhuang + **/ +public interface WriteHolder extends ConfigurationHolder { + /** + * What 'ExcelWriteHeadProperty' does the currently operated cell need to execute + * + * @return + */ + ExcelWriteHeadProperty excelWriteHeadProperty(); + + /** + * Is to determine if a field needs to be ignored + * + * @param fieldName + * @param columnIndex + * @return + */ + boolean ignore(String fieldName, Integer columnIndex); + + /** + * Whether a header is required for the currently operated cell + * + * @return + */ + boolean needHead(); + + /** + * Whether need automatic merge headers. + * + * @return + */ + boolean automaticMergeHead(); + + /** + * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. + * + * @return + */ + int relativeHeadRowIndex(); + + /** + * Data will be order by {@link #includeColumnFieldNames} or {@link #includeColumnIndexes}. + * + * default is false. + * + * @return + */ + + boolean orderByIncludeColumn(); + + /** + * Only output the custom columns. + * + * @return + */ + Collection includeColumnIndexes(); + + /** + * Only output the custom columns. + * + * @return + */ + Collection includeColumnFieldNames(); + + /** + * Ignore the custom columns. + * + * @return + */ + Collection excludeColumnIndexes(); + + /** + * Ignore the custom columns. + * + * @return + */ + Collection excludeColumnFieldNames(); +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteSheetHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteSheetHolder.java new file mode 100644 index 0000000..0788b0d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteSheetHolder.java @@ -0,0 +1,134 @@ +package ai.chat2db.excel.write.metadata.holder; + +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.enums.WriteLastRowTypeEnum; +import ai.chat2db.excel.util.StringUtils; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFSheet; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class WriteSheetHolder extends AbstractWriteHolder { + /** + * current param + */ + private WriteSheet writeSheet; + /*** + * Current poi Sheet.This is only for writing, and there may be no data in version 07 when template data needs to be + * read. + *

    + *
  • 03:{@link HSSFSheet}
  • + *
  • 07:{@link SXSSFSheet}
  • + *
+ */ + private Sheet sheet; + /*** + * Current poi Sheet.Be sure to use and this method when reading template data. + *
    + *
  • 03:{@link HSSFSheet}
  • + *
  • 07:{@link XSSFSheet}
  • + *
+ */ + private Sheet cachedSheet; + /*** + * sheetNo + */ + private Integer sheetNo; + /*** + * sheetName + */ + private String sheetName; + /*** + * poi sheet + */ + private WriteWorkbookHolder parentWriteWorkbookHolder; + /*** + * has been initialized table + */ + private Map hasBeenInitializedTable; + + /** + * last column type + * + * @param writeSheet + * @param writeWorkbookHolder + */ + private WriteLastRowTypeEnum writeLastRowTypeEnum; + + /** + * last row index + */ + private Integer lastRowIndex; + + public WriteSheetHolder(WriteSheet writeSheet, WriteWorkbookHolder writeWorkbookHolder) { + super(writeSheet, writeWorkbookHolder); + + // init handler + initHandler(writeSheet, writeWorkbookHolder); + + this.writeSheet = writeSheet; + if (writeSheet.getSheetNo() == null && StringUtils.isEmpty(writeSheet.getSheetName())) { + this.sheetNo = 0; + } else { + this.sheetNo = writeSheet.getSheetNo(); + } + this.sheetName = writeSheet.getSheetName(); + this.parentWriteWorkbookHolder = writeWorkbookHolder; + this.hasBeenInitializedTable = new HashMap<>(); + if (writeWorkbookHolder.getTempTemplateInputStream() != null) { + writeLastRowTypeEnum = WriteLastRowTypeEnum.TEMPLATE_EMPTY; + } else { + writeLastRowTypeEnum = WriteLastRowTypeEnum.COMMON_EMPTY; + } + lastRowIndex = 0; + } + + /** + * Get the last line of index, you have to make sure that the data is written next + * + * @return + */ + public int getNewRowIndexAndStartDoWrite() { + // 'getLastRowNum' doesn't matter if it has one or zero, it's zero + int newRowIndex = 0; + switch (writeLastRowTypeEnum) { + case TEMPLATE_EMPTY: + newRowIndex = Math.max(sheet.getLastRowNum(), cachedSheet.getLastRowNum()); + if (newRowIndex != 0 || cachedSheet.getRow(0) != null) { + newRowIndex++; + } + break; + case HAS_DATA: + newRowIndex = Math.max(sheet.getLastRowNum(), cachedSheet.getLastRowNum()); + newRowIndex++; + break; + default: + break; + } + writeLastRowTypeEnum = WriteLastRowTypeEnum.HAS_DATA; + return newRowIndex; + } + + @Override + public HolderEnum holderType() { + return HolderEnum.SHEET; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteTableHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteTableHolder.java new file mode 100644 index 0000000..a5270d0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteTableHolder.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.write.metadata.holder; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.write.metadata.WriteTable; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * sheet holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteTableHolder extends AbstractWriteHolder { + /*** + * poi sheet + */ + private WriteSheetHolder parentWriteSheetHolder; + /*** + * tableNo + */ + private Integer tableNo; + /** + * current table param + */ + private WriteTable writeTable; + + public WriteTableHolder(WriteTable writeTable, WriteSheetHolder writeSheetHolder) { + super(writeTable, writeSheetHolder); + this.parentWriteSheetHolder = writeSheetHolder; + this.tableNo = writeTable.getTableNo(); + this.writeTable = writeTable; + + // init handler + initHandler(writeTable, writeSheetHolder); + } + + @Override + public HolderEnum holderType() { + return HolderEnum.TABLE; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteWorkbookHolder.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteWorkbookHolder.java new file mode 100644 index 0000000..d2fb79d --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -0,0 +1,375 @@ +package ai.chat2db.excel.write.metadata.holder; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.enums.HolderEnum; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.IoUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.util.StyleUtil; +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; +import ai.chat2db.excel.write.metadata.WriteWorkbook; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString.Exclude; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +/** + * Workbook holder + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@Slf4j +public class WriteWorkbookHolder extends AbstractWriteHolder { + /*** + * Current poi Workbook.This is only for writing, and there may be no data in version 07 when template data needs to + * be read. + *
    + *
  • 03:{@link HSSFWorkbook}
  • + *
  • 07:{@link SXSSFWorkbook}
  • + *
+ */ + private Workbook workbook; + /*** + * Current poi Workbook.Be sure to use and this method when reading template data. + *
    + *
  • 03:{@link HSSFWorkbook}
  • + *
  • 07:{@link XSSFWorkbook}
  • + *
+ */ + private Workbook cachedWorkbook; + /** + * current param + */ + private WriteWorkbook writeWorkbook; + /** + * Final output file + *

+ * If 'outputStream' and 'file' all not empty, file first + */ + private File file; + /** + * Final output stream + */ + private OutputStream outputStream; + /** + * charset. + * Only work on the CSV file + */ + private Charset charset; + + /** + * Set the encoding prefix in the csv file, otherwise the office may open garbled characters. + * Default true. + */ + private Boolean withBom; + + /** + * Template input stream + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private InputStream templateInputStream; + /** + * Template file + *

+ * If 'inputStream' and 'file' all not empty, file first + */ + private File templateFile; + /** + * Temporary template file stream. + *

+ * A temporary file stream needs to be created in order not to modify the original template file. + */ + private InputStream tempTemplateInputStream; + /** + * Default true + */ + private Boolean autoCloseStream; + /** + * Excel type + */ + private ExcelTypeEnum excelType; + /** + * Mandatory use 'inputStream' + */ + private Boolean mandatoryUseInputStream; + /** + * prevent duplicate creation of sheet objects + */ + private Map hasBeenInitializedSheetIndexMap; + /** + * prevent duplicate creation of sheet objects + */ + private Map hasBeenInitializedSheetNameMap; + /** + * Whether the encryption + */ + private String password; + /** + * Write excel in memory. Default false, the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; + + /** + * Used to cell style. + */ + private Map> cellStyleIndexMap; + /** + * Used to font. + */ + private Map fontMap; + /** + * Used to data format. + */ + private Map dataFormatMap; + + /** + * handler context + */ + @Exclude + private WorkbookWriteHandlerContext workbookWriteHandlerContext; + + public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { + super(writeWorkbook, null); + this.writeWorkbook = writeWorkbook; + this.file = writeWorkbook.getFile(); + if (file != null) { + try { + this.outputStream = new FileOutputStream(file); + } catch (FileNotFoundException e) { + throw new ExcelGenerateException("Can not found file.", e); + } + } else { + this.outputStream = writeWorkbook.getOutputStream(); + } + + if (writeWorkbook.getCharset() == null) { + this.charset = Charset.defaultCharset(); + } else { + this.charset = writeWorkbook.getCharset(); + } + + if (writeWorkbook.getWithBom() == null) { + this.withBom = Boolean.TRUE; + } else { + this.withBom = writeWorkbook.getWithBom(); + } + + if (writeWorkbook.getAutoCloseStream() == null) { + this.autoCloseStream = Boolean.TRUE; + } else { + this.autoCloseStream = writeWorkbook.getAutoCloseStream(); + } + if (writeWorkbook.getExcelType() == null) { + boolean isXls = (file != null && file.getName().endsWith(ExcelTypeEnum.XLS.getValue())) + || (writeWorkbook.getTemplateFile() != null + && writeWorkbook.getTemplateFile().getName().endsWith(ExcelTypeEnum.XLS.getValue())); + if (isXls) { + this.excelType = ExcelTypeEnum.XLS; + } else { + boolean isCsv = (file != null && file.getName().endsWith(ExcelTypeEnum.CSV.getValue())) + || (writeWorkbook.getTemplateFile() != null + && writeWorkbook.getTemplateFile().getName().endsWith(ExcelTypeEnum.CSV.getValue())); + if (isCsv) { + this.excelType = ExcelTypeEnum.CSV; + } else { + this.excelType = ExcelTypeEnum.XLSX; + } + } + } else { + this.excelType = writeWorkbook.getExcelType(); + } + + // init handler + initHandler(writeWorkbook, null); + + try { + copyTemplate(); + } catch (IOException e) { + throw new ExcelGenerateException("Copy template failure.", e); + } + if (writeWorkbook.getMandatoryUseInputStream() == null) { + this.mandatoryUseInputStream = Boolean.FALSE; + } else { + this.mandatoryUseInputStream = writeWorkbook.getMandatoryUseInputStream(); + } + this.hasBeenInitializedSheetIndexMap = new HashMap<>(); + this.hasBeenInitializedSheetNameMap = new HashMap<>(); + this.password = writeWorkbook.getPassword(); + if (writeWorkbook.getInMemory() == null) { + this.inMemory = Boolean.FALSE; + } else { + this.inMemory = writeWorkbook.getInMemory(); + } + if (writeWorkbook.getWriteExcelOnException() == null) { + this.writeExcelOnException = Boolean.FALSE; + } else { + this.writeExcelOnException = writeWorkbook.getWriteExcelOnException(); + } + this.cellStyleIndexMap = MapUtils.newHashMap(); + this.fontMap = MapUtils.newHashMap(); + this.dataFormatMap = MapUtils.newHashMap(); + } + + private void copyTemplate() throws IOException { + if (writeWorkbook.getTemplateFile() == null && writeWorkbook.getTemplateInputStream() == null) { + return; + } + if (this.excelType == ExcelTypeEnum.CSV) { + throw new ExcelGenerateException("csv cannot use template."); + } + byte[] templateFileByte = null; + if (writeWorkbook.getTemplateFile() != null) { + templateFileByte = FileUtils.readFileToByteArray(writeWorkbook.getTemplateFile()); + } else if (writeWorkbook.getTemplateInputStream() != null) { + try { + templateFileByte = IoUtils.toByteArray(writeWorkbook.getTemplateInputStream()); + } finally { + if (autoCloseStream) { + writeWorkbook.getTemplateInputStream().close(); + } + } + } + this.tempTemplateInputStream = new ByteArrayInputStream(templateFileByte); + } + + @Override + public HolderEnum holderType() { + return HolderEnum.WORKBOOK; + } + + /** + * create a cell style. + * + * @param writeCellStyle + * @param originCellStyle + * @return + */ + public CellStyle createCellStyle(WriteCellStyle writeCellStyle, CellStyle originCellStyle) { + if (writeCellStyle == null) { + return originCellStyle; + } + + short styleIndex = -1; + Font originFont = null; + boolean useCache = true; + if (originCellStyle != null) { + styleIndex = originCellStyle.getIndex(); + if (originCellStyle instanceof XSSFCellStyle) { + originFont = ((XSSFCellStyle)originCellStyle).getFont(); + } else if (originCellStyle instanceof HSSFCellStyle) { + originFont = ((HSSFCellStyle)originCellStyle).getFont(workbook); + } + useCache = false; + } + + Map cellStyleMap = cellStyleIndexMap.computeIfAbsent(styleIndex, + key -> MapUtils.newHashMap()); + CellStyle cellStyle = cellStyleMap.get(writeCellStyle); + if (cellStyle != null) { + return cellStyle; + } + if (log.isDebugEnabled()) { + log.info("create new style:{},{}", writeCellStyle, originCellStyle); + } + WriteCellStyle tempWriteCellStyle = new WriteCellStyle(); + WriteCellStyle.merge(writeCellStyle, tempWriteCellStyle); + + cellStyle = StyleUtil.buildCellStyle(workbook, originCellStyle, tempWriteCellStyle); + Short dataFormat = createDataFormat(tempWriteCellStyle.getDataFormatData(), useCache); + if (dataFormat != null) { + cellStyle.setDataFormat(dataFormat); + } + Font font = createFont(tempWriteCellStyle.getWriteFont(), originFont, useCache); + if (font != null) { + cellStyle.setFont(font); + } + cellStyleMap.put(tempWriteCellStyle, cellStyle); + return cellStyle; + } + + /** + * create a font. + * + * @param writeFont + * @param originFont + * @param useCache + * @return + */ + public Font createFont(WriteFont writeFont, Font originFont, boolean useCache) { + if (!useCache) { + return StyleUtil.buildFont(workbook, originFont, writeFont); + } + WriteFont tempWriteFont = new WriteFont(); + WriteFont.merge(writeFont, tempWriteFont); + + Font font = fontMap.get(tempWriteFont); + if (font != null) { + return font; + } + font = StyleUtil.buildFont(workbook, originFont, tempWriteFont); + fontMap.put(tempWriteFont, font); + return font; + } + + /** + * create a data format. + * + * @param dataFormatData + * @param useCache + * @return + */ + public Short createDataFormat(DataFormatData dataFormatData, boolean useCache) { + if (dataFormatData == null) { + return null; + } + if (!useCache) { + return StyleUtil.buildDataFormat(workbook, dataFormatData); + } + DataFormatData tempDataFormatData = new DataFormatData(); + DataFormatData.merge(dataFormatData, tempDataFormatData); + + Short dataFormat = dataFormatMap.get(tempDataFormatData); + if (dataFormat != null) { + return dataFormat; + } + dataFormat = StyleUtil.buildDataFormat(workbook, tempDataFormatData); + dataFormatMap.put(tempDataFormatData, dataFormat); + return dataFormat; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteCellStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteCellStyle.java new file mode 100644 index 0000000..72d4211 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteCellStyle.java @@ -0,0 +1,360 @@ +package ai.chat2db.excel.write.metadata.style; + +import ai.chat2db.excel.constant.BuiltinFormats; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.property.FontProperty; +import ai.chat2db.excel.metadata.property.StyleProperty; +import ai.chat2db.excel.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IgnoredErrorType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * Cell style when writing + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteCellStyle { + /** + * Set the data format (must be a valid format). Built in formats are defined at {@link BuiltinFormats}. + */ + private DataFormatData dataFormatData; + + /** + * Set the font for this style + */ + private WriteFont writeFont; + /** + * Set the cell's using this style to be hidden + */ + private Boolean hidden; + + /** + * Set the cell's using this style to be locked + */ + private Boolean locked; + /** + * Turn on or off "Quote Prefix" or "123 Prefix" for the style, which is used to tell Excel that the thing which + * looks like a number or a formula shouldn't be treated as on. Turning this on is somewhat (but not completely, see + * {@link IgnoredErrorType}) like prefixing the cell value with a ' in Excel + */ + private Boolean quotePrefix; + /** + * Set the type of horizontal alignment for the cell + */ + private HorizontalAlignment horizontalAlignment; + /** + * Set whether the text should be wrapped. Setting this flag to true make all content visible within a + * cell by displaying it on multiple lines + */ + private Boolean wrapped; + /** + * Set the type of vertical alignment for the cell + */ + private VerticalAlignment verticalAlignment; + /** + * Set the degree of rotation for the text in the cell. + * + * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF uses values from 0 to 180 degrees. The + * implementations of this method will map between these two value-ranges accordingly, however the corresponding + * getter is returning values in the range mandated by the current type of Excel file-format that this CellStyle is + * applied to. + */ + private Short rotation; + /** + * Set the number of spaces to indent the text in the cell + */ + private Short indent; + /** + * Set the type of border to use for the left border of the cell + */ + private BorderStyle borderLeft; + /** + * Set the type of border to use for the right border of the cell + */ + private BorderStyle borderRight; + /** + * Set the type of border to use for the top border of the cell + */ + private BorderStyle borderTop; + + /** + * Set the type of border to use for the bottom border of the cell + */ + private BorderStyle borderBottom; + /** + * Set the color to use for the left border + * + * @see IndexedColors + */ + private Short leftBorderColor; + + /** + * Set the color to use for the right border + * + * @see IndexedColors + */ + private Short rightBorderColor; + + /** + * Set the color to use for the top border + * + * @see IndexedColors + */ + private Short topBorderColor; + /** + * Set the color to use for the bottom border + * + * @see IndexedColors + */ + private Short bottomBorderColor; + /** + * Setting to one fills the cell with the foreground color... No idea about other values + * + * @see FillPatternType#SOLID_FOREGROUND + */ + private FillPatternType fillPatternType; + + /** + * Set the background fill color. + * + * @see IndexedColors + */ + private Short fillBackgroundColor; + + /** + * Set the foreground fill color Note: Ensure Foreground color is set prior to background color. + * + * @see IndexedColors + */ + private Short fillForegroundColor; + /** + * Controls if the Cell should be auto-sized to shrink to fit if the text is too long + */ + private Boolean shrinkToFit; + + /** + * The source is not empty merge the data to the target. + * + * @param source source + * @param target target + */ + public static void merge(WriteCellStyle source, WriteCellStyle target) { + if (source == null || target == null) { + return; + } + if (source.getDataFormatData() != null) { + if (target.getDataFormatData() == null) { + target.setDataFormatData(source.getDataFormatData()); + } else { + DataFormatData.merge(source.getDataFormatData(), target.getDataFormatData()); + } + } + if (source.getWriteFont() != null) { + if (target.getWriteFont() == null) { + target.setWriteFont(source.getWriteFont()); + } else { + WriteFont.merge(source.getWriteFont(), target.getWriteFont()); + } + } + if (source.getHidden() != null) { + target.setHidden(source.getHidden()); + } + if (source.getLocked() != null) { + target.setLocked(source.getLocked()); + } + if (source.getQuotePrefix() != null) { + target.setQuotePrefix(source.getQuotePrefix()); + } + if (source.getHorizontalAlignment() != null) { + target.setHorizontalAlignment(source.getHorizontalAlignment()); + } + if (source.getWrapped() != null) { + target.setWrapped(source.getWrapped()); + } + if (source.getVerticalAlignment() != null) { + target.setVerticalAlignment(source.getVerticalAlignment()); + } + if (source.getRotation() != null) { + target.setRotation(source.getRotation()); + } + if (source.getIndent() != null) { + target.setIndent(source.getIndent()); + } + if (source.getBorderLeft() != null) { + target.setBorderLeft(source.getBorderLeft()); + } + if (source.getBorderRight() != null) { + target.setBorderRight(source.getBorderRight()); + } + if (source.getBorderTop() != null) { + target.setBorderTop(source.getBorderTop()); + } + if (source.getBorderBottom() != null) { + target.setBorderBottom(source.getBorderBottom()); + } + if (source.getLeftBorderColor() != null) { + target.setLeftBorderColor(source.getLeftBorderColor()); + } + if (source.getRightBorderColor() != null) { + target.setRightBorderColor(source.getRightBorderColor()); + } + if (source.getTopBorderColor() != null) { + target.setTopBorderColor(source.getTopBorderColor()); + } + if (source.getBottomBorderColor() != null) { + target.setBottomBorderColor(source.getBottomBorderColor()); + } + if (source.getFillPatternType() != null) { + target.setFillPatternType(source.getFillPatternType()); + } + if (source.getFillBackgroundColor() != null) { + target.setFillBackgroundColor(source.getFillBackgroundColor()); + } + if (source.getFillForegroundColor() != null) { + target.setFillForegroundColor(source.getFillForegroundColor()); + } + if (source.getShrinkToFit() != null) { + target.setShrinkToFit(source.getShrinkToFit()); + } + } + + /** + * The source is not empty merge the data to the target. + * + * @param styleProperty styleProperty + * @param fontProperty fontProperty + */ + public static WriteCellStyle build(StyleProperty styleProperty, FontProperty fontProperty) { + if (styleProperty == null && fontProperty == null) { + return null; + } + WriteCellStyle writeCellStyle = new WriteCellStyle(); + buildStyleProperty(styleProperty, writeCellStyle); + buildFontProperty(fontProperty, writeCellStyle); + return writeCellStyle; + } + + private static void buildFontProperty(FontProperty fontProperty, WriteCellStyle writeCellStyle) { + if (fontProperty == null) { + return; + } + if (writeCellStyle.getWriteFont() == null) { + writeCellStyle.setWriteFont(new WriteFont()); + } + WriteFont writeFont = writeCellStyle.getWriteFont(); + + if (StringUtils.isNotBlank(fontProperty.getFontName())) { + writeFont.setFontName(fontProperty.getFontName()); + } + if (fontProperty.getFontHeightInPoints() != null) { + writeFont.setFontHeightInPoints(fontProperty.getFontHeightInPoints()); + } + if (fontProperty.getItalic() != null) { + writeFont.setItalic(fontProperty.getItalic()); + } + if (fontProperty.getStrikeout() != null) { + writeFont.setStrikeout(fontProperty.getStrikeout()); + } + if (fontProperty.getColor() != null) { + writeFont.setColor(fontProperty.getColor()); + } + if (fontProperty.getTypeOffset() != null) { + writeFont.setTypeOffset(fontProperty.getTypeOffset()); + } + if (fontProperty.getUnderline() != null) { + writeFont.setUnderline(fontProperty.getUnderline()); + } + if (fontProperty.getCharset() != null) { + writeFont.setCharset(fontProperty.getCharset()); + } + if (fontProperty.getBold() != null) { + writeFont.setBold(fontProperty.getBold()); + } + } + + private static void buildStyleProperty(StyleProperty styleProperty, WriteCellStyle writeCellStyle) { + if (styleProperty == null) { + return; + } + if (styleProperty.getDataFormatData() != null) { + if (writeCellStyle.getDataFormatData() == null) { + writeCellStyle.setDataFormatData(styleProperty.getDataFormatData()); + } else { + DataFormatData.merge(styleProperty.getDataFormatData(), writeCellStyle.getDataFormatData()); + } + } + if (styleProperty.getHidden() != null) { + writeCellStyle.setHidden(styleProperty.getHidden()); + } + if (styleProperty.getLocked() != null) { + writeCellStyle.setLocked(styleProperty.getLocked()); + } + if (styleProperty.getQuotePrefix() != null) { + writeCellStyle.setQuotePrefix(styleProperty.getQuotePrefix()); + } + if (styleProperty.getHorizontalAlignment() != null) { + writeCellStyle.setHorizontalAlignment(styleProperty.getHorizontalAlignment()); + } + if (styleProperty.getWrapped() != null) { + writeCellStyle.setWrapped(styleProperty.getWrapped()); + } + if (styleProperty.getVerticalAlignment() != null) { + writeCellStyle.setVerticalAlignment(styleProperty.getVerticalAlignment()); + } + if (styleProperty.getRotation() != null) { + writeCellStyle.setRotation(styleProperty.getRotation()); + } + if (styleProperty.getIndent() != null) { + writeCellStyle.setIndent(styleProperty.getIndent()); + } + if (styleProperty.getBorderLeft() != null) { + writeCellStyle.setBorderLeft(styleProperty.getBorderLeft()); + } + if (styleProperty.getBorderRight() != null) { + writeCellStyle.setBorderRight(styleProperty.getBorderRight()); + } + if (styleProperty.getBorderTop() != null) { + writeCellStyle.setBorderTop(styleProperty.getBorderTop()); + } + if (styleProperty.getBorderBottom() != null) { + writeCellStyle.setBorderBottom(styleProperty.getBorderBottom()); + } + if (styleProperty.getLeftBorderColor() != null) { + writeCellStyle.setLeftBorderColor(styleProperty.getLeftBorderColor()); + } + if (styleProperty.getRightBorderColor() != null) { + writeCellStyle.setRightBorderColor(styleProperty.getRightBorderColor()); + } + if (styleProperty.getTopBorderColor() != null) { + writeCellStyle.setTopBorderColor(styleProperty.getTopBorderColor()); + } + if (styleProperty.getBottomBorderColor() != null) { + writeCellStyle.setBottomBorderColor(styleProperty.getBottomBorderColor()); + } + if (styleProperty.getFillPatternType() != null) { + writeCellStyle.setFillPatternType(styleProperty.getFillPatternType()); + } + if (styleProperty.getFillBackgroundColor() != null) { + writeCellStyle.setFillBackgroundColor(styleProperty.getFillBackgroundColor()); + } + if (styleProperty.getFillForegroundColor() != null) { + writeCellStyle.setFillForegroundColor(styleProperty.getFillForegroundColor()); + } + if (styleProperty.getShrinkToFit() != null) { + writeCellStyle.setShrinkToFit(styleProperty.getShrinkToFit()); + } + + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteFont.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteFont.java new file mode 100644 index 0000000..2fb86c8 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/metadata/style/WriteFont.java @@ -0,0 +1,118 @@ +package ai.chat2db.excel.write.metadata.style; + +import ai.chat2db.excel.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hssf.usermodel.HSSFPalette; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * Font when writing + * + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteFont { + /** + * The name for the font (i.e. Arial) + */ + private String fontName; + /** + * Height in the familiar unit of measure - points + */ + private Short fontHeightInPoints; + /** + * Whether to use italics or not + */ + private Boolean italic; + /** + * Whether to use a strikeout horizontal line through the text or not + */ + private Boolean strikeout; + /** + * The color for the font + * + * @see Font#COLOR_NORMAL + * @see Font#COLOR_RED + * @see HSSFPalette#getColor(short) + * @see IndexedColors + */ + private Short color; + /** + * Set normal, super or subscript. + * + * @see Font#SS_NONE + * @see Font#SS_SUPER + * @see Font#SS_SUB + */ + private Short typeOffset; + /** + * set type of text underlining to use + * + * @see Font#U_NONE + * @see Font#U_SINGLE + * @see Font#U_DOUBLE + * @see Font#U_SINGLE_ACCOUNTING + * @see Font#U_DOUBLE_ACCOUNTING + */ + + private Byte underline; + /** + * Set character-set to use. + * + * @see FontCharset + * @see Font#ANSI_CHARSET + * @see Font#DEFAULT_CHARSET + * @see Font#SYMBOL_CHARSET + */ + private Integer charset; + /** + * Bold + */ + private Boolean bold; + + /** + * The source is not empty merge the data to the target. + * + * @param source source + * @param target target + */ + public static void merge(WriteFont source, WriteFont target) { + if (source == null || target == null) { + return; + } + if (StringUtils.isNotBlank(source.getFontName())) { + target.setFontName(source.getFontName()); + } + if (source.getFontHeightInPoints() != null) { + target.setFontHeightInPoints(source.getFontHeightInPoints()); + } + if (source.getItalic() != null) { + target.setItalic(source.getItalic()); + } + if (source.getStrikeout() != null) { + target.setStrikeout(source.getStrikeout()); + } + if (source.getColor() != null) { + target.setColor(source.getColor()); + } + if (source.getTypeOffset() != null) { + target.setTypeOffset(source.getTypeOffset()); + } + if (source.getUnderline() != null) { + target.setUnderline(source.getUnderline()); + } + if (source.getCharset() != null) { + target.setCharset(source.getCharset()); + } + if (source.getBold() != null) { + target.setBold(source.getBold()); + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/property/ExcelWriteHeadProperty.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/property/ExcelWriteHeadProperty.java new file mode 100644 index 0000000..b9ed10a --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/property/ExcelWriteHeadProperty.java @@ -0,0 +1,148 @@ +package ai.chat2db.excel.write.property; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ai.chat2db.excel.enums.HeadKindEnum; +import ai.chat2db.excel.metadata.CellRange; +import ai.chat2db.excel.metadata.ConfigurationHolder; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentLoopMerge; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.annotation.write.style.OnceAbsoluteMerge; +import ai.chat2db.excel.metadata.property.ColumnWidthProperty; +import ai.chat2db.excel.metadata.property.ExcelHeadProperty; +import ai.chat2db.excel.metadata.property.FontProperty; +import ai.chat2db.excel.metadata.property.LoopMergeProperty; +import ai.chat2db.excel.metadata.property.OnceAbsoluteMergeProperty; +import ai.chat2db.excel.metadata.property.RowHeightProperty; +import ai.chat2db.excel.metadata.property.StyleProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Define the header attribute of excel + * + * @author jipengfei + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcelWriteHeadProperty extends ExcelHeadProperty { + + private RowHeightProperty headRowHeightProperty; + private RowHeightProperty contentRowHeightProperty; + private OnceAbsoluteMergeProperty onceAbsoluteMergeProperty; + + public ExcelWriteHeadProperty(ConfigurationHolder configurationHolder, Class headClazz, List> head) { + super(configurationHolder, headClazz, head); + if (getHeadKind() != HeadKindEnum.CLASS) { + return; + } + this.headRowHeightProperty = + RowHeightProperty.build(headClazz.getAnnotation(HeadRowHeight.class)); + this.contentRowHeightProperty = + RowHeightProperty.build(headClazz.getAnnotation(ContentRowHeight.class)); + this.onceAbsoluteMergeProperty = + OnceAbsoluteMergeProperty.build(headClazz.getAnnotation(OnceAbsoluteMerge.class)); + + ColumnWidth parentColumnWidth = headClazz.getAnnotation(ColumnWidth.class); + HeadStyle parentHeadStyle = headClazz.getAnnotation(HeadStyle.class); + HeadFontStyle parentHeadFontStyle = headClazz.getAnnotation(HeadFontStyle.class); + + + for (Map.Entry entry : getHeadMap().entrySet()) { + Head headData = entry.getValue(); + if (headData == null) { + throw new IllegalArgumentException( + "Passing in the class and list the head, the two must be the same size."); + } + Field field = headData.getField(); + + ColumnWidth columnWidth = field.getAnnotation(ColumnWidth.class); + if (columnWidth == null) { + columnWidth = parentColumnWidth; + } + headData.setColumnWidthProperty(ColumnWidthProperty.build(columnWidth)); + + + HeadStyle headStyle = field.getAnnotation(HeadStyle.class); + if (headStyle == null) { + headStyle = parentHeadStyle; + } + headData.setHeadStyleProperty(StyleProperty.build(headStyle)); + + HeadFontStyle headFontStyle = field.getAnnotation(HeadFontStyle.class); + if (headFontStyle == null) { + headFontStyle = parentHeadFontStyle; + } + headData.setHeadFontProperty(FontProperty.build(headFontStyle)); + + headData.setLoopMergeProperty(LoopMergeProperty.build(field.getAnnotation(ContentLoopMerge.class))); + } + } + + /** + * Calculate all cells that need to be merged + * + * @return cells that need to be merged + */ + public List headCellRangeList() { + List cellRangeList = new ArrayList(); + Set alreadyRangeSet = new HashSet(); + List headList = new ArrayList(getHeadMap().values()); + for (int i = 0; i < headList.size(); i++) { + Head head = headList.get(i); + List headNameList = head.getHeadNameList(); + for (int j = 0; j < headNameList.size(); j++) { + if (alreadyRangeSet.contains(i + "-" + j)) { + continue; + } + alreadyRangeSet.add(i + "-" + j); + String headName = headNameList.get(j); + int lastCol = i; + int lastRow = j; + for (int k = i + 1; k < headList.size(); k++) { + String key = k + "-" + j; + if (headList.get(k).getHeadNameList().get(j).equals(headName) && !alreadyRangeSet.contains(key)) { + alreadyRangeSet.add(key); + lastCol = k; + } else { + break; + } + } + Set tempAlreadyRangeSet = new HashSet<>(); + outer: + for (int k = j + 1; k < headNameList.size(); k++) { + for (int l = i; l <= lastCol; l++) { + String key = l + "-" + k; + if (headList.get(l).getHeadNameList().get(k).equals(headName) && !alreadyRangeSet.contains( + key)) { + tempAlreadyRangeSet.add(l + "-" + k); + } else { + break outer; + } + } + lastRow = k; + alreadyRangeSet.addAll(tempAlreadyRangeSet); + } + if (j == lastRow && i == lastCol) { + continue; + } + cellRangeList + .add(new CellRange(j, lastRow, head.getColumnIndex(), headList.get(lastCol).getColumnIndex())); + } + } + return cellRangeList; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractCellStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractCellStyleStrategy.java new file mode 100644 index 0000000..62947ae --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractCellStyleStrategy.java @@ -0,0 +1,74 @@ +package ai.chat2db.excel.write.style; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.constant.OrderConstant; +import ai.chat2db.excel.metadata.Head; + +import org.apache.poi.ss.usermodel.Cell; + +/** + * Cell style strategy + * + * @author Jiaju Zhuang + */ +public abstract class AbstractCellStyleStrategy implements CellWriteHandler { + + @Override + public int order() { + return OrderConstant.DEFINE_STYLE; + } + + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + if (context.getHead() == null) { + return; + } + if (context.getHead()) { + setHeadCellStyle(context); + } else { + setContentCellStyle(context); + } + } + + /** + * Sets the cell style of header + * + * @param context + */ + protected void setHeadCellStyle(CellWriteHandlerContext context) { + setHeadCellStyle(context.getCell(), context.getHeadData(), context.getRelativeRowIndex()); + } + + /** + * Sets the cell style of header + * + * @param cell + * @param head + * @param relativeRowIndex + */ + protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) { + throw new UnsupportedOperationException("Custom styles must override the setHeadCellStyle method."); + } + + /** + * Sets the cell style of content + * + * @param context + */ + protected void setContentCellStyle(CellWriteHandlerContext context) { + setContentCellStyle(context.getCell(), context.getHeadData(), context.getRelativeRowIndex()); + } + + /** + * Sets the cell style of content + * + * @param cell + * @param head + * @param relativeRowIndex + */ + protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) { + throw new UnsupportedOperationException("Custom styles must override the setContentCellStyle method."); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractVerticalCellStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractVerticalCellStyleStrategy.java new file mode 100644 index 0000000..f8a78bc --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/AbstractVerticalCellStyleStrategy.java @@ -0,0 +1,79 @@ +package ai.chat2db.excel.write.style; + +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; + +/** + * Use the same style for the column + * + * @author Jiaju Zhuang + */ +public abstract class AbstractVerticalCellStyleStrategy extends AbstractCellStyleStrategy { + + @Override + protected void setHeadCellStyle(CellWriteHandlerContext context) { + if (stopProcessing(context)) { + return; + } + WriteCellData cellData = context.getFirstCellData(); + WriteCellStyle.merge(headCellStyle(context), cellData.getOrCreateStyle()); + } + + @Override + protected void setContentCellStyle(CellWriteHandlerContext context) { + if (context.getFirstCellData() == null) { + return; + } + WriteCellData cellData = context.getFirstCellData(); + WriteCellStyle.merge(contentCellStyle(context), cellData.getOrCreateStyle()); + } + + /** + * Returns the column width corresponding to each column head + * + * @param context + * @return + */ + protected WriteCellStyle headCellStyle(CellWriteHandlerContext context) { + return headCellStyle(context.getHeadData()); + } + + /** + * Returns the column width corresponding to each column head + * + * @param head Nullable + * @return + */ + protected WriteCellStyle headCellStyle(Head head) { + return null; + } + + /** + * Returns the column width corresponding to each column head. + * + * @param context + * @return + */ + protected WriteCellStyle contentCellStyle(CellWriteHandlerContext context) { + return contentCellStyle(context.getHeadData()); + } + + /** + * Returns the column width corresponding to each column head + * + * @param head Nullable + * @return + */ + protected WriteCellStyle contentCellStyle(Head head) { + return null; + } + + protected boolean stopProcessing(CellWriteHandlerContext context) { + if (context.getFirstCellData() == null) { + return true; + } + return context.getHeadData() == null; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/DefaultStyle.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/DefaultStyle.java new file mode 100644 index 0000000..6d903c2 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/DefaultStyle.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.write.style; + +import ai.chat2db.excel.constant.OrderConstant; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * The default styles + * + * @author Jiaju Zhuang + */ +public class DefaultStyle extends HorizontalCellStyleStrategy { + + @Override + public int order() { + return OrderConstant.DEFAULT_DEFINE_STYLE; + } + + public DefaultStyle() { + super(); + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + headWriteCellStyle.setWrapped(true); + headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + headWriteCellStyle.setLocked(true); + headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + headWriteCellStyle.setBorderTop(BorderStyle.THIN); + headWriteCellStyle.setBorderBottom(BorderStyle.THIN); + headWriteCellStyle.setBorderLeft(BorderStyle.THIN); + headWriteCellStyle.setBorderRight(BorderStyle.THIN); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontName("宋体"); + headWriteFont.setFontHeightInPoints((short)14); + headWriteFont.setBold(true); + headWriteCellStyle.setWriteFont(headWriteFont); + + setHeadWriteCellStyle(headWriteCellStyle); + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/HorizontalCellStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/HorizontalCellStyleStrategy.java new file mode 100644 index 0000000..1c9a030 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/HorizontalCellStyleStrategy.java @@ -0,0 +1,72 @@ +package ai.chat2db.excel.write.style; + +import java.util.List; + +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; + +/** + * Use the same style for the row + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class HorizontalCellStyleStrategy extends AbstractCellStyleStrategy { + + private WriteCellStyle headWriteCellStyle; + private List contentWriteCellStyleList; + + public HorizontalCellStyleStrategy() { + } + + public HorizontalCellStyleStrategy(WriteCellStyle headWriteCellStyle, + List contentWriteCellStyleList) { + this.headWriteCellStyle = headWriteCellStyle; + this.contentWriteCellStyleList = contentWriteCellStyleList; + } + + public HorizontalCellStyleStrategy(WriteCellStyle headWriteCellStyle, WriteCellStyle contentWriteCellStyle) { + this.headWriteCellStyle = headWriteCellStyle; + if (contentWriteCellStyle != null) { + this.contentWriteCellStyleList = ListUtils.newArrayList(contentWriteCellStyle); + } + } + + @Override + protected void setHeadCellStyle(CellWriteHandlerContext context) { + if (stopProcessing(context) || headWriteCellStyle == null) { + return; + } + WriteCellData cellData = context.getFirstCellData(); + WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle()); + } + + @Override + protected void setContentCellStyle(CellWriteHandlerContext context) { + if (stopProcessing(context) || CollectionUtils.isEmpty(contentWriteCellStyleList)) { + return; + } + WriteCellData cellData = context.getFirstCellData(); + if (context.getRelativeRowIndex() == null || context.getRelativeRowIndex() <= 0) { + WriteCellStyle.merge(contentWriteCellStyleList.get(0), cellData.getOrCreateStyle()); + } else { + WriteCellStyle.merge( + contentWriteCellStyleList.get(context.getRelativeRowIndex() % contentWriteCellStyleList.size()), + cellData.getOrCreateStyle()); + } + } + + protected boolean stopProcessing(CellWriteHandlerContext context) { + return context.getFirstCellData() == null; + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractColumnWidthStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractColumnWidthStyleStrategy.java new file mode 100644 index 0000000..b41a895 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractColumnWidthStyleStrategy.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.write.style.column; + +import java.util.List; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; + +import org.apache.poi.ss.usermodel.Cell; + +/** + * Column width style strategy + * + * @author Jiaju Zhuang + */ +public abstract class AbstractColumnWidthStyleStrategy implements CellWriteHandler { + + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + setColumnWidth(context); + } + + /** + * Sets the column width when head create + * + * @param context + */ + protected void setColumnWidth(CellWriteHandlerContext context) { + setColumnWidth(context.getWriteSheetHolder(), context.getCellDataList(), context.getCell(), + context.getHeadData(), context.getRelativeRowIndex(), context.getHead()); + } + + /** + * Sets the column width when head create + * + * @param writeSheetHolder + * @param cellDataList + * @param cell + * @param head + * @param relativeRowIndex + * @param isHead + */ + protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, + Head head, Integer relativeRowIndex, Boolean isHead) { + throw new UnsupportedOperationException("Custom styles must override the setColumnWidth method."); + } + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java new file mode 100644 index 0000000..fb11ec1 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.write.style.column; + +import java.util.List; + +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; + +import org.apache.poi.ss.usermodel.Cell; + +/** + * Returns the column width according to each column header + * + * @author Jiaju Zhuang + */ +public abstract class AbstractHeadColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy { + + @Override + protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, Head head, + Integer relativeRowIndex, Boolean isHead) { + boolean needSetWidth = relativeRowIndex != null && (isHead || relativeRowIndex == 0); + if (!needSetWidth) { + return; + } + Integer width = columnWidth(head, cell.getColumnIndex()); + if (width != null) { + width = width * 256; + writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), width); + } + } + + /** + * Returns the column width corresponding to each column head. + * + *

+ * if return null, ignore + * + * @param head + * Nullable. + * @param columnIndex + * Not null. + * @return + */ + protected abstract Integer columnWidth(Head head, Integer columnIndex); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java new file mode 100644 index 0000000..9547f56 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java @@ -0,0 +1,73 @@ +package ai.chat2db.excel.write.style.column; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; + +/** + * Take the width of the longest column as the width. + *

+ * This is not very useful at the moment, for example if you have Numbers it will cause a newline.And the length is not + * exactly the same as the actual length. + * + * @author Jiaju Zhuang + */ +public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy { + + private static final int MAX_COLUMN_WIDTH = 255; + + private final Map> cache = MapUtils.newHashMapWithExpectedSize(8); + + @Override + protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, + Head head, + Integer relativeRowIndex, Boolean isHead) { + boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList); + if (!needSetWidth) { + return; + } + Map maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(), key -> new HashMap<>(16)); + Integer columnWidth = dataLength(cellDataList, cell, isHead); + if (columnWidth < 0) { + return; + } + if (columnWidth > MAX_COLUMN_WIDTH) { + columnWidth = MAX_COLUMN_WIDTH; + } + Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex()); + if (maxColumnWidth == null || columnWidth > maxColumnWidth) { + maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth); + writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256); + } + } + + private Integer dataLength(List> cellDataList, Cell cell, Boolean isHead) { + if (isHead) { + return cell.getStringCellValue().getBytes().length; + } + WriteCellData cellData = cellDataList.get(0); + CellDataTypeEnum type = cellData.getType(); + if (type == null) { + return -1; + } + switch (type) { + case STRING: + return cellData.getStringValue().getBytes().length; + case BOOLEAN: + return cellData.getBooleanValue().toString().getBytes().length; + case NUMBER: + return cellData.getNumberValue().toString().getBytes().length; + default: + return -1; + } + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/SimpleColumnWidthStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/SimpleColumnWidthStyleStrategy.java new file mode 100644 index 0000000..6807bf0 --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/column/SimpleColumnWidthStyleStrategy.java @@ -0,0 +1,25 @@ +package ai.chat2db.excel.write.style.column; + +import ai.chat2db.excel.metadata.Head; + +/** + * All the columns are the same width + * + * @author Jiaju Zhuang + */ +public class SimpleColumnWidthStyleStrategy extends AbstractHeadColumnWidthStyleStrategy { + private final Integer columnWidth; + + /** + * + * @param columnWidth + */ + public SimpleColumnWidthStyleStrategy(Integer columnWidth) { + this.columnWidth = columnWidth; + } + + @Override + protected Integer columnWidth(Head head, Integer columnIndex) { + return columnWidth; + } +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/AbstractRowHeightStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/AbstractRowHeightStyleStrategy.java new file mode 100644 index 0000000..09a4c0e --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/AbstractRowHeightStyleStrategy.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.write.style.row; + +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; + +import org.apache.poi.ss.usermodel.Row; + +/** + * Set the row height strategy + * + * @author Jiaju Zhuang + */ +public abstract class AbstractRowHeightStyleStrategy implements RowWriteHandler { + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + if (context.getHead() == null) { + return; + } + if (context.getHead()) { + setHeadColumnHeight(context.getRow(), context.getRelativeRowIndex()); + } else { + setContentColumnHeight(context.getRow(), context.getRelativeRowIndex()); + } + } + + /** + * Sets the height of header + * + * @param row + * @param relativeRowIndex + */ + protected abstract void setHeadColumnHeight(Row row, int relativeRowIndex); + + /** + * Sets the height of content + * + * @param row + * @param relativeRowIndex + */ + protected abstract void setContentColumnHeight(Row row, int relativeRowIndex); + +} diff --git a/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/SimpleRowHeightStyleStrategy.java b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/SimpleRowHeightStyleStrategy.java new file mode 100644 index 0000000..d05a7eb --- /dev/null +++ b/easyexcel-plus-core/src/main/java/ai/chat2db/excel/write/style/row/SimpleRowHeightStyleStrategy.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.write.style.row; + +import org.apache.poi.ss.usermodel.Row; + +/** + * Set the head column high and content column high + * + * @author Jiaju Zhuang + */ +public class SimpleRowHeightStyleStrategy extends AbstractRowHeightStyleStrategy { + private final Short headRowHeight; + private final Short contentRowHeight; + + public SimpleRowHeightStyleStrategy(Short headRowHeight, Short contentRowHeight) { + this.headRowHeight = headRowHeight; + this.contentRowHeight = contentRowHeight; + } + + @Override + protected void setHeadColumnHeight(Row row, int relativeRowIndex) { + if (headRowHeight != null) { + row.setHeightInPoints(headRowHeight); + } + } + + @Override + protected void setContentColumnHeight(Row row, int relativeRowIndex) { + if (contentRowHeight != null) { + row.setHeightInPoints(contentRowHeight); + } + } + +} diff --git a/easyexcel-plus-support/README.md b/easyexcel-plus-support/README.md new file mode 100644 index 0000000..580f05d --- /dev/null +++ b/easyexcel-plus-support/README.md @@ -0,0 +1,3 @@ +# easyexcel-support + +外部依赖的代码,目前就一个cglib,由于cglib不支持jdk高版本,所以单独复制了一份 \ No newline at end of file diff --git a/easyexcel-plus-support/pom.xml b/easyexcel-plus-support/pom.xml new file mode 100644 index 0000000..5c24678 --- /dev/null +++ b/easyexcel-plus-support/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + + ai.chat2db.excel + easyexcel-plus-parent + 1.0.0-SNAPSHOT + ../pom.xml + + + https://github.com/alibaba/easyexcel + jar + easyexcel-plus-support + easyexcel-plus-support + 1.0.1-SNAPSHOT + + + 1.0.1-SNAPSHOT + UTF-8 + 1.8 + true + true + true + + + + + org.springframework + spring-core + 5.3.37 + + + * + * + + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + true + false + true + + true + + + org.springframework:spring-core + + + + + org.springframework:spring-core + + org/springframework/asm/** + org/springframework/cglib/** + + + + + + org.springframework + ai.chat2db.excel.support + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + flatten.process-resources + process-resources + + flatten + + + + flatten + package + + flatten + + + + + + + + diff --git a/easyexcel-plus-support/src/main/java/ai/chat2db/excel/support/Empty.java b/easyexcel-plus-support/src/main/java/ai/chat2db/excel/support/Empty.java new file mode 100644 index 0000000..34265f5 --- /dev/null +++ b/easyexcel-plus-support/src/main/java/ai/chat2db/excel/support/Empty.java @@ -0,0 +1,9 @@ +package ai.chat2db.excel.support; + +/** + * empty + * + * @author Jiaju Zhuang + */ +public class Empty { +} diff --git a/easyexcel-plus-test/README.md b/easyexcel-plus-test/README.md new file mode 100644 index 0000000..299588a --- /dev/null +++ b/easyexcel-plus-test/README.md @@ -0,0 +1,3 @@ +# easyexcel-test + +测试案例 \ No newline at end of file diff --git a/easyexcel-plus-test/pom.xml b/easyexcel-plus-test/pom.xml new file mode 100644 index 0000000..14436ac --- /dev/null +++ b/easyexcel-plus-test/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + ai.chat2db.excel + easyexcel-plus-parent + 1.0.0-SNAPSHOT + ../pom.xml + + + https://github.com/alibaba/easyexcel + jar + easyexcel-plus-test + easyexcel-plus-test + + + true + + + + + + ai.chat2db.excel + easyexcel-plus-core + 1.0.0-SNAPSHOT + + + com.alibaba.fastjson2 + fastjson2 + 2.0.51 + + + org.springframework.boot + spring-boot-starter-web + 2.7.18 + + + org.springframework.boot + spring-boot-starter-test + 2.7.18 + + + + + org.slf4j + slf4j-simple + 1.7.36 + + + org.slf4j + jcl-over-slf4j + 1.7.36 + + + org.slf4j + log4j-over-slf4j + 1.7.36 + + + ch.qos.logback + logback-classic + 1.5.6 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + /com/alibaba/easyexcel/test/core/**/*.java + + false + + + + + diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/StyleTestUtils.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/StyleTestUtils.java new file mode 100644 index 0000000..f0b9953 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/StyleTestUtils.java @@ -0,0 +1,43 @@ +package ai.chat2db.excel.test.core; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFCell; + +public class StyleTestUtils { + + public static byte[] getFillForegroundColor(Cell cell) { + if (cell instanceof XSSFCell) { + return ((XSSFCell)cell).getCellStyle().getFillForegroundColorColor().getRGB(); + } else { + return short2byte(((HSSFCell)cell).getCellStyle().getFillForegroundColorColor().getTriplet()); + } + } + + public static byte[] getFontColor(Cell cell, Workbook workbook) { + if (cell instanceof XSSFCell) { + return ((XSSFCell)cell).getCellStyle().getFont().getXSSFColor().getRGB(); + } else { + return short2byte(((HSSFCell)cell).getCellStyle().getFont(workbook).getHSSFColor((HSSFWorkbook)workbook) + .getTriplet()); + } + } + + public static short getFontHeightInPoints(Cell cell, Workbook workbook) { + if (cell instanceof XSSFCell) { + return ((XSSFCell)cell).getCellStyle().getFont().getFontHeightInPoints(); + } else { + return ((HSSFCell)cell).getCellStyle().getFont(workbook).getFontHeightInPoints(); + } + } + + private static byte[] short2byte(short[] shorts) { + byte[] bytes = new byte[shorts.length]; + for (int i = 0; i < shorts.length; i++) { + bytes[i] = (byte)shorts[i]; + } + return bytes; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationData.java new file mode 100644 index 0000000..0cf1580 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationData.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.core.annotation; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadRowHeight; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@ColumnWidth(50) +@HeadRowHeight(50) +@ContentRowHeight(100) +public class AnnotationData { + @ExcelProperty("日期") + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + private Date date; + + @ExcelProperty(value = "数字") + @NumberFormat("#.##%") + private Double number; + + @ExcelIgnore + private String ignore; + private static final String staticFinal = "test"; + private transient String transientString; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataListener.java new file mode 100644 index 0000000..17a0af0 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataListener.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.test.core.annotation; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelCommonException; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class AnnotationDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(AnnotationData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + AnnotationData data = list.get(0); + try { + Assertions.assertEquals(data.getDate(), DateUtils.parseDate("2020-01-01 01:01:01")); + } catch (ParseException e) { + throw new ExcelCommonException("Test Exception", e); + } + Assertions.assertEquals(data.getNumber(), 99.99, 0.00); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataTest.java new file mode 100644 index 0000000..b9489da --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationDataTest.java @@ -0,0 +1,134 @@ +package ai.chat2db.excel.test.core.annotation; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.test.core.StyleTestUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class AnnotationDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File fileStyle07; + private static File fileStyle03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("annotation07.xlsx"); + file03 = TestFileUtil.createNewFile("annotation03.xls"); + fileStyle07 = TestFileUtil.createNewFile("annotationStyle07.xlsx"); + fileStyle03 = TestFileUtil.createNewFile("annotationStyle03.xls"); + fileCsv = TestFileUtil.createNewFile("annotationCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + @Test + public void t11WriteStyle07() throws Exception { + writeStyle(fileStyle07); + } + + @Test + public void t12Write03() throws Exception { + writeStyle(fileStyle03); + } + + private void writeStyle(File file) throws Exception { + EasyExcel.write().file(file).head(AnnotationStyleData.class).sheet().doWrite(dataStyle()); + + Workbook workbook = WorkbookFactory.create(file); + Sheet sheet = workbook.getSheetAt(0); + + Row row0 = sheet.getRow(0); + Cell cell00 = row0.getCell(0); + Assertions.assertArrayEquals(new byte[] {-1, 0, -1}, StyleTestUtils.getFillForegroundColor(cell00)); + Assertions.assertArrayEquals(new byte[] {-1, -52, 0}, StyleTestUtils.getFontColor(cell00, workbook)); + Assertions.assertEquals(40, StyleTestUtils.getFontHeightInPoints(cell00, workbook)); + + Cell cell01 = row0.getCell(1); + Assertions.assertArrayEquals(new byte[] {-1, 0, 0}, StyleTestUtils.getFillForegroundColor(cell01)); + Assertions.assertArrayEquals(new byte[] {0, -1, -1}, StyleTestUtils.getFontColor(cell01, workbook)); + Assertions.assertEquals(20, StyleTestUtils.getFontHeightInPoints(cell01, workbook)); + + Row row1 = sheet.getRow(1); + Cell cell10 = row1.getCell(0); + Assertions.assertArrayEquals(new byte[] {0, -52, -1}, StyleTestUtils.getFillForegroundColor(cell10)); + Assertions.assertArrayEquals(new byte[] {0, 0, -1}, StyleTestUtils.getFontColor(cell10, workbook)); + Assertions.assertEquals(50, StyleTestUtils.getFontHeightInPoints(cell10, workbook)); + Cell cell11 = row1.getCell(1); + Assertions.assertArrayEquals(new byte[] {0, -128, 0}, StyleTestUtils.getFillForegroundColor(cell11)); + Assertions.assertArrayEquals(new byte[] {-64, -64, -64}, StyleTestUtils.getFontColor(cell11, workbook)); + Assertions.assertEquals(30, StyleTestUtils.getFontHeightInPoints(cell11, workbook)); + } + + private void readAndWrite(File file) throws Exception { + EasyExcel.write().file(file).head(AnnotationData.class).sheet().doWrite(dataStyle()); + + if (file == fileCsv) { + return; + } + + Workbook workbook = WorkbookFactory.create(file); + Sheet sheet = workbook.getSheetAt(0); + Assertions.assertEquals(50 * 256, sheet.getColumnWidth(0), 0); + + Row row0 = sheet.getRow(0); + Assertions.assertEquals(1000, row0.getHeight(), 0); + + Row row1 = sheet.getRow(1); + Assertions.assertEquals(2000, row1.getHeight(), 0); + } + + private List dataStyle() throws Exception { + List list = new ArrayList<>(); + AnnotationStyleData data = new AnnotationStyleData(); + data.setString("string"); + data.setString1("string1"); + list.add(data); + return list; + } + + private List data() throws Exception { + List list = new ArrayList<>(); + AnnotationData data = new AnnotationData(); + data.setDate(DateUtils.parseDate("2020-01-01 01:01:01")); + data.setNumber(99.99); + data.setIgnore("忽略"); + data.setTransientString("忽略"); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameData.java new file mode 100644 index 0000000..7c756a7 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameData.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.test.core.annotation; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class AnnotationIndexAndNameData { + @ExcelProperty(value = "第四个", index = 4) + private String index4; + @ExcelProperty(value = "第二个") + private String index2; + @ExcelProperty(index = 0) + private String index0; + @ExcelProperty(value = "第一个", index = 1) + private String index1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataListener.java new file mode 100644 index 0000000..0fdf9e3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataListener.java @@ -0,0 +1,36 @@ +package ai.chat2db.excel.test.core.annotation; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class AnnotationIndexAndNameDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationIndexAndNameDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(AnnotationIndexAndNameData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + AnnotationIndexAndNameData data = list.get(0); + Assertions.assertEquals(data.getIndex0(), "第0个"); + Assertions.assertEquals(data.getIndex1(), "第1个"); + Assertions.assertEquals(data.getIndex2(), "第2个"); + Assertions.assertEquals(data.getIndex4(), "第4个"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataTest.java new file mode 100644 index 0000000..d52faa3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationIndexAndNameDataTest.java @@ -0,0 +1,65 @@ +package ai.chat2db.excel.test.core.annotation; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Annotation data test + * + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class AnnotationIndexAndNameDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("annotationIndexAndName07.xlsx"); + file03 = TestFileUtil.createNewFile("annotationIndexAndName03.xls"); + fileCsv = TestFileUtil.createNewFile("annotationIndexAndNameCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, AnnotationIndexAndNameData.class).sheet().doWrite(data()); + EasyExcel.read(file, AnnotationIndexAndNameData.class, new AnnotationIndexAndNameDataListener()).sheet() + .doRead(); + } + + private List data() { + List list = new ArrayList(); + AnnotationIndexAndNameData data = new AnnotationIndexAndNameData(); + data.setIndex0("第0个"); + data.setIndex1("第1个"); + data.setIndex2("第2个"); + data.setIndex4("第4个"); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationStyleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationStyleData.java new file mode 100644 index 0000000..eb62a46 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/annotation/AnnotationStyleData.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.core.annotation; + +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ContentFontStyle; +import ai.chat2db.excel.annotation.write.style.ContentStyle; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10) +@HeadFontStyle(fontHeightInPoints = 20, color = 15) +@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17) +@ContentFontStyle(fontHeightInPoints = 30, color = 22) +public class AnnotationStyleData { + @ExcelProperty("字符串") + @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14) + @HeadFontStyle(fontHeightInPoints = 40, color = 51) + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) + @ContentFontStyle(fontHeightInPoints = 50, color = 12) + private String string; + @ExcelProperty("字符串1") + private String string1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomData.java new file mode 100644 index 0000000..0fb438a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomData.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.test.core.bom; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class BomData { + @ExcelProperty("姓名") + private String name; + @ExcelProperty("年纪") + private Long age; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomDataTest.java new file mode 100644 index 0000000..818f20b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/bom/BomDataTest.java @@ -0,0 +1,126 @@ +package ai.chat2db.excel.test.core.bom; + +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * bom test + * + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class BomDataTest { + + @Test + public void t01ReadCsv() { + readCsv(TestFileUtil.readFile("bom" + File.separator + "no_bom.csv")); + readCsv(TestFileUtil.readFile("bom" + File.separator + "office_bom.csv")); + } + + @Test + public void t02ReadAndWriteCsv() throws Exception { + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_default.csv"), null, null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_utf_8.csv"), "UTF-8", null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_utf_8_lower_case.csv"), "utf-8", null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_gbk.csv"), "GBK", null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_gbk_lower_case.csv"), "gbk", null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_utf_16be.csv"), "UTF-16BE", null); + readAndWriteCsv(TestFileUtil.createNewFile("bom" + File.separator + "bom_utf_8_not_with_bom.csv"), "UTF-8", + Boolean.FALSE); + } + + private void readAndWriteCsv(File file, String charsetName, Boolean withBom) throws Exception { + Charset charset = null; + if (charsetName != null) { + charset = Charset.forName(charsetName); + } + EasyExcel.write(new FileOutputStream(file), BomData.class) + .charset(charset) + .withBom(withBom) + .excelType(ExcelTypeEnum.CSV) + .sheet() + .doWrite(data()); + + EasyExcel.read(file, BomData.class, new ReadListener() { + + private final List dataList = Lists.newArrayList(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + String head = headMap.get(0).getStringValue(); + Assertions.assertEquals("姓名", head); + } + + @Override + public void invoke(BomData data, AnalysisContext context) { + dataList.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(dataList.size(), 10); + BomData bomData = dataList.get(0); + Assertions.assertEquals("姓名0", bomData.getName()); + Assertions.assertEquals(20, (long)bomData.getAge()); + } + }) + .charset(charset) + .sheet().doRead(); + } + + private void readCsv(File file) { + EasyExcel.read(file, BomData.class, new ReadListener() { + + private final List dataList = Lists.newArrayList(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + String head = headMap.get(0).getStringValue(); + Assertions.assertEquals("姓名", head); + } + + @Override + public void invoke(BomData data, AnalysisContext context) { + dataList.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(dataList.size(), 10); + BomData bomData = dataList.get(0); + Assertions.assertEquals("姓名0", bomData.getName()); + Assertions.assertEquals(20L, (long)bomData.getAge()); + } + }).sheet().doRead(); + } + + private List data() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + BomData data = new BomData(); + data.setName("姓名" + i); + data.setAge(20L); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheData.java new file mode 100644 index 0000000..7ca395d --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.core.cache; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CacheData { + @ExcelProperty("姓名") + private String name; + + @ExcelProperty("年龄") + private Long age; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheDataTest.java new file mode 100644 index 0000000..0fafe15 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheDataTest.java @@ -0,0 +1,219 @@ +package ai.chat2db.excel.test.core.cache; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.CacheLocationEnum; +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.read.listener.PageReadListener; +import ai.chat2db.excel.util.ClassUtils; +import ai.chat2db.excel.util.FieldUtils; +import ai.chat2db.excel.test.demo.read.DemoData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.FieldCache; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class CacheDataTest { + + private static File file07; + private static File fileCacheInvoke; + private static File fileCacheInvoke2; + private static File fileCacheInvokeMemory; + private static File fileCacheInvokeMemory2; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("cache/cache.xlsx"); + fileCacheInvoke = TestFileUtil.createNewFile("cache/fileCacheInvoke.xlsx"); + fileCacheInvoke2 = TestFileUtil.createNewFile("cache/fileCacheInvoke2.xlsx"); + fileCacheInvokeMemory = TestFileUtil.createNewFile("cache/fileCacheInvokeMemory.xlsx"); + fileCacheInvokeMemory2 = TestFileUtil.createNewFile("cache/fileCacheInvokeMemory2.xlsx"); + } + + @Test + public void t01ReadAndWrite() throws Exception { + Field field = FieldUtils.getField(ClassUtils.class, "FIELD_THREAD_LOCAL", true); + ThreadLocal, FieldCache>> fieldThreadLocal = (ThreadLocal, FieldCache>>)field.get( + ClassUtils.class.newInstance()); + Assertions.assertNull(fieldThreadLocal.get()); + EasyExcel.write(file07, CacheData.class).sheet().doWrite(data()); + EasyExcel.read(file07, CacheData.class, new PageReadListener(dataList -> { + Assertions.assertNotNull(fieldThreadLocal.get()); + })) + .sheet() + .doRead(); + Assertions.assertNull(fieldThreadLocal.get()); + } + + @Test + public void t02ReadAndWriteInvoke() throws Exception { + EasyExcel.write(fileCacheInvoke, CacheInvokeData.class).sheet().doWrite(dataInvoke()); + EasyExcel.read(fileCacheInvoke, CacheInvokeData.class, new AnalysisEventListener() { + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + Assertions.assertEquals(2, headMap.size()); + Assertions.assertEquals("姓名", headMap.get(0)); + Assertions.assertEquals("年龄", headMap.get(1)); + } + + @Override + public void invoke(CacheInvokeData data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + }).sheet() + .doRead(); + + Field name = FieldUtils.getField(CacheInvokeData.class, "name", true); + ExcelProperty annotation = name.getAnnotation(ExcelProperty.class); + InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation); + Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues"); + memberValues.setAccessible(true); + Map map = (Map)memberValues.get(invocationHandler); + map.put("value", new String[] {"姓名2"}); + + EasyExcel.write(fileCacheInvoke2, CacheInvokeData.class).sheet().doWrite(dataInvoke()); + EasyExcel.read(fileCacheInvoke2, CacheInvokeData.class, new AnalysisEventListener() { + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + Assertions.assertEquals(2, headMap.size()); + Assertions.assertEquals("姓名2", headMap.get(0)); + Assertions.assertEquals("年龄", headMap.get(1)); + } + + @Override + public void invoke(CacheInvokeData data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + }).sheet() + .doRead(); + + } + + @Test + public void t03ReadAndWriteInvokeMemory() throws Exception { + EasyExcel.write(fileCacheInvokeMemory, CacheInvokeMemoryData.class) + .filedCacheLocation(CacheLocationEnum.MEMORY) + .sheet() + .doWrite(dataInvokeMemory()); + EasyExcel.read(fileCacheInvokeMemory, CacheInvokeMemoryData.class, + new AnalysisEventListener() { + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + Assertions.assertEquals(2, headMap.size()); + Assertions.assertEquals("姓名", headMap.get(0)); + Assertions.assertEquals("年龄", headMap.get(1)); + } + + @Override + public void invoke(CacheInvokeMemoryData data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + }) + .filedCacheLocation(CacheLocationEnum.MEMORY) + .sheet() + .doRead(); + + Field name = FieldUtils.getField(CacheInvokeMemoryData.class, "name", true); + ExcelProperty annotation = name.getAnnotation(ExcelProperty.class); + InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation); + Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues"); + memberValues.setAccessible(true); + Map map = (Map)memberValues.get(invocationHandler); + map.put("value", new String[] {"姓名2"}); + + EasyExcel.write(fileCacheInvokeMemory2, CacheInvokeMemoryData.class) + .filedCacheLocation(CacheLocationEnum.MEMORY) + .sheet() + .doWrite(dataInvokeMemory()); + EasyExcel.read(fileCacheInvokeMemory2, CacheInvokeMemoryData.class, + new AnalysisEventListener() { + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + Assertions.assertEquals(2, headMap.size()); + Assertions.assertEquals("姓名", headMap.get(0)); + Assertions.assertEquals("年龄", headMap.get(1)); + } + + @Override + public void invoke(CacheInvokeMemoryData data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + }) + .filedCacheLocation(CacheLocationEnum.MEMORY) + .sheet() + .doRead(); + + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + CacheData simpleData = new CacheData(); + simpleData.setName("姓名" + i); + simpleData.setAge((long)i); + list.add(simpleData); + } + return list; + } + + private List dataInvoke() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + CacheInvokeData simpleData = new CacheInvokeData(); + simpleData.setName("姓名" + i); + simpleData.setAge((long)i); + list.add(simpleData); + } + return list; + } + + private List dataInvokeMemory() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + CacheInvokeMemoryData simpleData = new CacheInvokeMemoryData(); + simpleData.setName("姓名" + i); + simpleData.setAge((long)i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeData.java new file mode 100644 index 0000000..60c78b4 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.core.cache; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CacheInvokeData { + @ExcelProperty("姓名") + private String name; + + @ExcelProperty("年龄") + private Long age; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeMemoryData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeMemoryData.java new file mode 100644 index 0000000..a4d8851 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/cache/CacheInvokeMemoryData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.core.cache; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CacheInvokeMemoryData { + @ExcelProperty("姓名") + private String name; + + @ExcelProperty("年龄") + private Long age; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataListener.java new file mode 100644 index 0000000..4505673 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataListener.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.test.core.celldata; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.support.ExcelTypeEnum; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class CellDataDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(CellDataDataListener.class); + List list = new ArrayList<>(); + + @Override + public void invoke(CellDataReadData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + CellDataReadData cellDataData = list.get(0); + + Assertions.assertEquals("2020年01月01日", cellDataData.getDate().getData()); + Assertions.assertEquals((long)cellDataData.getInteger1().getData(), 2L); + Assertions.assertEquals((long)cellDataData.getInteger2(), 2L); + if (context.readWorkbookHolder().getExcelType() != ExcelTypeEnum.CSV) { + Assertions.assertEquals(cellDataData.getFormulaValue().getFormulaData().getFormulaValue(), "B2+C2"); + } else { + Assertions.assertNull(cellDataData.getFormulaValue().getData()); + } + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataTest.java new file mode 100644 index 0000000..65a7dc8 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataDataTest.java @@ -0,0 +1,74 @@ +package ai.chat2db.excel.test.core.celldata; + +import java.io.File; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.data.FormulaData; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class CellDataDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("cellData07.xlsx"); + file03 = TestFileUtil.createNewFile("cellData03.xls"); + fileCsv = TestFileUtil.createNewFile("cellDataCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) throws Exception { + EasyExcel.write(file, CellDataWriteData.class).sheet().doWrite(data()); + EasyExcel.read(file, CellDataReadData.class, new CellDataDataListener()).sheet().doRead(); + } + + private List data() throws Exception { + List list = new ArrayList<>(); + CellDataWriteData cellDataData = new CellDataWriteData(); + cellDataData.setDate(new WriteCellData<>(DateUtils.parseDate("2020-01-01 01:01:01"))); + WriteCellData integer1 = new WriteCellData<>(); + integer1.setType(CellDataTypeEnum.NUMBER); + integer1.setNumberValue(BigDecimal.valueOf(2L)); + cellDataData.setInteger1(integer1); + cellDataData.setInteger2(2); + WriteCellData formulaValue = new WriteCellData<>(); + FormulaData formulaData = new FormulaData(); + formulaValue.setFormulaData(formulaData); + formulaData.setFormulaValue("B2+C2"); + cellDataData.setFormulaValue(formulaValue); + list.add(cellDataData); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataReadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataReadData.java new file mode 100644 index 0000000..e1978be --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataReadData.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.core.celldata; + +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.metadata.data.ReadCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellDataReadData { + @DateTimeFormat("yyyy年MM月dd日") + private ReadCellData date; + private ReadCellData integer1; + private Integer integer2; + private ReadCellData formulaValue; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataWriteData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataWriteData.java new file mode 100644 index 0000000..0a6c1fa --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/celldata/CellDataWriteData.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.test.core.celldata; + +import java.util.Date; + +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CellDataWriteData { + @DateTimeFormat("yyyy年MM月dd日") + private WriteCellData date; + private WriteCellData integer1; + private Integer integer2; + private WriteCellData formulaValue; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetData.java new file mode 100644 index 0000000..dc4dcfd --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetData.java @@ -0,0 +1,17 @@ +package ai.chat2db.excel.test.core.charset; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class CharsetData { + @ExcelProperty("姓名") + private String name; + @ExcelProperty("年纪") + private Integer age; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetDataTest.java new file mode 100644 index 0000000..5c12e21 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/charset/CharsetDataTest.java @@ -0,0 +1,110 @@ +package ai.chat2db.excel.test.core.charset; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.data.ReadCellData; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * charset + * + * @author Jiaju Zhuang + */ +@Slf4j +@TestMethodOrder(MethodOrderer.MethodName.class) +public class CharsetDataTest { + private static final Charset GBK = Charset.forName("GBK"); + private static File fileCsvGbk; + private static File fileCsvUtf8; + private static File fileCsvError; + + @BeforeAll + public static void init() { + fileCsvGbk = TestFileUtil.createNewFile("charset" + File.separator + "fileCsvGbk.csv"); + fileCsvUtf8 = TestFileUtil.createNewFile("charset" + File.separator + "fileCsvUtf8.csv"); + fileCsvError = TestFileUtil.createNewFile("charset" + File.separator + "fileCsvError.csv"); + } + + @Test + public void t01ReadAndWriteCsv() { + readAndWrite(fileCsvGbk, GBK); + readAndWrite(fileCsvUtf8, StandardCharsets.UTF_8); + } + + @Test + public void t02ReadAndWriteCsvError() { + EasyExcel.write(fileCsvError, CharsetData.class).charset(GBK).sheet().doWrite(data()); + EasyExcel.read(fileCsvError, CharsetData.class, new ReadListener() { + + private final List dataList = Lists.newArrayList(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + String head = headMap.get(0).getStringValue(); + Assertions.assertNotEquals("姓名", head); + } + + @Override + public void invoke(CharsetData data, AnalysisContext context) { + dataList.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } + }).charset(StandardCharsets.UTF_8).sheet().doRead(); + } + + private void readAndWrite(File file, Charset charset) { + EasyExcel.write(file, CharsetData.class).charset(charset).sheet().doWrite(data()); + EasyExcel.read(file, CharsetData.class, new ReadListener() { + + private final List dataList = Lists.newArrayList(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + String head = headMap.get(0).getStringValue(); + Assertions.assertEquals("姓名", head); + } + + @Override + public void invoke(CharsetData data, AnalysisContext context) { + dataList.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(dataList.size(), 10); + CharsetData charsetData = dataList.get(0); + Assertions.assertEquals("姓名0", charsetData.getName()); + Assertions.assertEquals(0, (long)charsetData.getAge()); + } + }).charset(charset).sheet().doRead(); + } + + private List data() { + List list = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + CharsetData data = new CharsetData(); + data.setName("姓名" + i); + data.setAge(i); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/compatibility/CompatibilityTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/compatibility/CompatibilityTest.java new file mode 100644 index 0000000..673e135 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/compatibility/CompatibilityTest.java @@ -0,0 +1,171 @@ +package ai.chat2db.excel.test.core.compatibility; + +import java.io.File; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.cache.Ehcache; +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.test.core.simple.SimpleData; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.util.TempFile; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Compatible with some special files + * + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class CompatibilityTest { + + @Test + public void t01() { + // https://github.com/alibaba/easyexcel/issues/2236 + List> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t01.xls").sheet() + .doReadSync(); + Assertions.assertEquals(2, list.size()); + Map row1 = list.get(1); + Assertions.assertEquals("Q235(碳钢)", row1.get(0)); + } + + @Test + public void t02() { + // Exist in `sharedStrings.xml` `x:t` start tag, need to be compatible + List> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t02.xlsx").sheet() + .headRowNumber(0).doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals(3, list.size()); + Map row2 = list.get(2); + Assertions.assertEquals("1,2-戊二醇", row2.get(2)); + } + + @Test + public void t03() { + // In the presence of the first line of a lot of null columns, ignore null columns + List> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t03.xlsx").sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals(1, list.size()); + Map row0 = list.get(0); + Assertions.assertEquals(12, row0.size()); + } + + @Test + public void t04() { + // Exist in `sheet1.xml` `ns2:t` start tag, need to be compatible + List> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t04.xlsx").sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals(56, list.size()); + Map row0 = list.get(0); + Assertions.assertEquals("QQSJK28F152A012242S0081", row0.get(5)); + } + + @Test + public void t05() { + // https://github.com/alibaba/easyexcel/issues/1956 + // Excel read date needs to be rounded + List> list = EasyExcel + .read(TestFileUtil.getPath() + "compatibility/t05.xlsx") + .sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals("2023-01-01 00:00:00", list.get(0).get(0)); + Assertions.assertEquals("2023-01-01 00:00:00", list.get(1).get(0)); + Assertions.assertEquals("2023-01-01 00:00:00", list.get(2).get(0)); + Assertions.assertEquals("2023-01-01 00:00:01", list.get(3).get(0)); + Assertions.assertEquals("2023-01-01 00:00:01", list.get(4).get(0)); + } + + @Test + public void t06() { + // Keep error precision digital format + List> list = EasyExcel + .read(TestFileUtil.getPath() + "compatibility/t06.xlsx") + .headRowNumber(0) + .sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals("2087.03", list.get(0).get(2)); + } + + @Test + public void t07() { + // https://github.com/alibaba/easyexcel/issues/2805 + // Excel read date needs to be rounded + List> list = EasyExcel + .read(TestFileUtil.getPath() + "compatibility/t07.xlsx") + .readDefaultReturn(ReadDefaultReturnEnum.ACTUAL_DATA) + .sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals(0, new BigDecimal("24.1998124").compareTo((BigDecimal)list.get(0).get(11))); + + list = EasyExcel + .read(TestFileUtil.getPath() + "compatibility/t07.xlsx") + .sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals("24.20", list.get(0).get(11)); + } + + @Test + public void t08() { + // https://github.com/alibaba/easyexcel/issues/2693 + // Temporary files may be deleted if there is no operation for a long time, so they need to be recreated. + File file = TestFileUtil.createNewFile("compatibility/t08.xlsx"); + EasyExcel.write(file, SimpleData.class) + .sheet() + .doWrite(data()); + + List> list = EasyExcel.read(file) + .readCache(new Ehcache(null, 20)) + .sheet() + .doReadSync(); + Assertions.assertEquals(10L, list.size()); + + FileUtils.delete(new File(System.getProperty(TempFile.JAVA_IO_TMPDIR))); + + list = EasyExcel.read(file) + .readCache(new Ehcache(null, 20)) + .sheet() + .doReadSync(); + Assertions.assertEquals(10L, list.size()); + } + + @Test + public void t09() { + // `SH_x005f_x000D_Z002` exists in `ShardingString.xml` and needs to be replaced by: `SH_x000D_Z002` + File file = TestFileUtil.readFile("compatibility/t09.xlsx"); + List> list = EasyExcel.read(file) + .headRowNumber(0) + .sheet() + .doReadSync(); + log.info("data:{}", JSON.toJSONString(list)); + Assertions.assertEquals(1, list.size()); + + Assertions.assertEquals("SH_x000D_Z002", list.get(0).get(0)); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + SimpleData simpleData = new SimpleData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataListener.java new file mode 100644 index 0000000..cc70cec --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataListener.java @@ -0,0 +1,49 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.util.TestUtil; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ConverterDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ConverterDataListener.class); + private final List list = new ArrayList<>(); + + @Override + public void invoke(ConverterReadData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + ConverterReadData data = list.get(0); + Assertions.assertEquals(TestUtil.TEST_DATE, data.getDate()); + Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE, data.getLocalDate()); + Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE_TIME, data.getLocalDateTime()); + Assertions.assertEquals(data.getBooleanData(), Boolean.TRUE); + Assertions.assertEquals(data.getBigDecimal().doubleValue(), BigDecimal.ONE.doubleValue(), 0.0); + Assertions.assertEquals(data.getBigInteger().intValue(), BigInteger.ONE.intValue(), 0.0); + Assertions.assertEquals((long)data.getLongData(), 1L); + Assertions.assertEquals((long)data.getIntegerData(), 1L); + Assertions.assertEquals((long)data.getShortData(), 1L); + Assertions.assertEquals((long)data.getByteData(), 1L); + Assertions.assertEquals(data.getDoubleData(), 1.0, 0.0); + Assertions.assertEquals(data.getFloatData(), (float)1.0, 0.0); + Assertions.assertEquals(data.getString(), "测试"); + Assertions.assertEquals(data.getCellData().getStringValue(), "自定义"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataTest.java new file mode 100644 index 0000000..03fb322 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterDataTest.java @@ -0,0 +1,132 @@ +package ai.chat2db.excel.test.core.converter; + +import java.io.File; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.test.util.TestUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ConverterDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File fileImage07; + private static File fileImage03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("converter07.xlsx"); + file03 = TestFileUtil.createNewFile("converter03.xls"); + fileCsv = TestFileUtil.createNewFile("converterCsv.csv"); + fileImage07 = TestFileUtil.createNewFile("converterImage07.xlsx"); + fileImage03 = TestFileUtil.createNewFile("converterImage03.xls"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) throws Exception { + EasyExcel.write(file, ConverterWriteData.class).sheet().doWrite(data()); + EasyExcel.read(file, ConverterReadData.class, new ConverterDataListener()).sheet().doRead(); + } + + @Test + public void t11ReadAllConverter07() { + readAllConverter("converter" + File.separator + "converter07.xlsx"); + } + + @Test + public void t12ReadAllConverter03() { + readAllConverter("converter" + File.separator + "converter03.xls"); + } + + @Test + public void t13ReadAllConverterCsv() { + readAllConverter("converter" + File.separator + "converterCsv.csv"); + } + + @Test + public void t21WriteImage07() throws Exception { + writeImage(fileImage07); + } + + @Test + public void t22WriteImage03() throws Exception { + writeImage(fileImage03); + } + + private void writeImage(File file) throws Exception { + InputStream inputStream = null; + try { + List list = new ArrayList<>(); + ImageData imageData = new ImageData(); + list.add(imageData); + String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; + imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); + imageData.setFile(new File(imagePath)); + imageData.setString(imagePath); + inputStream = FileUtils.openInputStream(new File(imagePath)); + imageData.setInputStream(inputStream); + EasyExcel.write(file, ImageData.class).sheet().doWrite(list); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + private void readAllConverter(String fileName) { + EasyExcel.read(TestFileUtil.readFile(fileName), ReadAllConverterData.class, new ReadAllConverterDataListener()) + .sheet().doRead(); + } + + private List data() throws Exception { + List list = new ArrayList(); + ConverterWriteData converterWriteData = new ConverterWriteData(); + converterWriteData.setDate(TestUtil.TEST_DATE); + converterWriteData.setLocalDate(TestUtil.TEST_LOCAL_DATE); + converterWriteData.setLocalDateTime(TestUtil.TEST_LOCAL_DATE_TIME); + converterWriteData.setBooleanData(Boolean.TRUE); + converterWriteData.setBigDecimal(BigDecimal.ONE); + converterWriteData.setBigInteger(BigInteger.ONE); + converterWriteData.setLongData(1L); + converterWriteData.setIntegerData(1); + converterWriteData.setShortData((short)1); + converterWriteData.setByteData((byte)1); + converterWriteData.setDoubleData(1.0); + converterWriteData.setFloatData((float)1.0); + converterWriteData.setString("测试"); + converterWriteData.setCellData(new WriteCellData<>("自定义")); + list.add(converterWriteData); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterReadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterReadData.java new file mode 100644 index 0000000..70b9328 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterReadData.java @@ -0,0 +1,51 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.metadata.data.ReadCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ConverterReadData { + @ExcelProperty("日期") + private Date date; + @ExcelProperty("本地日期") + private LocalDate localDate; + @ExcelProperty("本地日期时间") + private LocalDateTime localDateTime; + @ExcelProperty("布尔") + private Boolean booleanData; + @ExcelProperty("大数") + private BigDecimal bigDecimal; + @ExcelProperty("大整数") + private BigInteger bigInteger; + @ExcelProperty("长整型") + private long longData; + @ExcelProperty("整型") + private Integer integerData; + @ExcelProperty("短整型") + private Short shortData; + @ExcelProperty("字节型") + private Byte byteData; + @ExcelProperty("双精度浮点型") + private double doubleData; + @ExcelProperty("浮点型") + private Float floatData; + @ExcelProperty("字符串") + private String string; + @ExcelProperty("自定义") + private ReadCellData cellData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterTest.java new file mode 100644 index 0000000..0c1724c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterTest.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; + +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.converters.floatconverter.FloatNumberConverter; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ConverterTest { + + @Test + public void t01FloatNumberConverter() { + FloatNumberConverter floatNumberConverter = new FloatNumberConverter(); + WriteConverterContext context = new WriteConverterContext<>(); + context.setValue(95.62F); + WriteCellData writeCellData = floatNumberConverter.convertToExcelData(context); + Assertions.assertEquals(0, writeCellData.getNumberValue().compareTo(new BigDecimal("95.62"))); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterWriteData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterWriteData.java new file mode 100644 index 0000000..c41f758 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ConverterWriteData.java @@ -0,0 +1,51 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ConverterWriteData { + @ExcelProperty("日期") + private Date date; + @ExcelProperty("本地日期") + private LocalDate localDate; + @ExcelProperty("本地日期时间") + private LocalDateTime localDateTime; + @ExcelProperty("布尔") + private Boolean booleanData; + @ExcelProperty("大数") + private BigDecimal bigDecimal; + @ExcelProperty("大整数") + private BigInteger bigInteger; + @ExcelProperty("长整型") + private long longData; + @ExcelProperty("整型") + private Integer integerData; + @ExcelProperty("短整型") + private Short shortData; + @ExcelProperty("字节型") + private Byte byteData; + @ExcelProperty("双精度浮点型") + private double doubleData; + @ExcelProperty("浮点型") + private Float floatData; + @ExcelProperty("字符串") + private String string; + @ExcelProperty("自定义") + private WriteCellData cellData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ImageData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ImageData.java new file mode 100644 index 0000000..54aad6c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ImageData.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.test.core.converter; + +import java.io.File; +import java.io.InputStream; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.converters.string.StringImageConverter; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(500) +@ColumnWidth(500 / 8) +public class ImageData { + private File file; + private InputStream inputStream; + @ExcelProperty(converter = StringImageConverter.class) + private String string; + private byte[] byteArray; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterData.java new file mode 100644 index 0000000..ec6466c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterData.java @@ -0,0 +1,57 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ReadAllConverterData { + private BigDecimal bigDecimalBoolean; + private BigDecimal bigDecimalNumber; + private BigDecimal bigDecimalString; + private BigInteger bigIntegerBoolean; + private BigInteger bigIntegerNumber; + private BigInteger bigIntegerString; + private Boolean booleanBoolean; + private Boolean booleanNumber; + private Boolean booleanString; + private Byte byteBoolean; + private Byte byteNumber; + private Byte byteString; + private Date dateNumber; + private Date dateString; + private LocalDateTime localDateTimeNumber; + private LocalDateTime localDateTimeString; + private Double doubleBoolean; + private Double doubleNumber; + private Double doubleString; + private Float floatBoolean; + private Float floatNumber; + private Float floatString; + private Integer integerBoolean; + private Integer integerNumber; + private Integer integerString; + private Long longBoolean; + private Long longNumber; + private Long longString; + private Short shortBoolean; + private Short shortNumber; + private Short shortString; + private String stringBoolean; + private String stringNumber; + private String stringString; + private String stringError; + private String stringFormulaNumber; + private String stringFormulaString; + private String stringNumberDate; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterDataListener.java new file mode 100644 index 0000000..fa4a2d3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/converter/ReadAllConverterDataListener.java @@ -0,0 +1,86 @@ +package ai.chat2db.excel.test.core.converter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelCommonException; +import ai.chat2db.excel.support.ExcelTypeEnum; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ReadAllConverterDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ReadAllConverterDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(ReadAllConverterData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + ReadAllConverterData data = list.get(0); + Assertions.assertEquals(data.getBigDecimalBoolean().doubleValue(), BigDecimal.ONE.doubleValue(), 0.0); + Assertions.assertEquals(data.getBigDecimalNumber().doubleValue(), BigDecimal.ONE.doubleValue(), 0.0); + Assertions.assertEquals(data.getBigDecimalString().doubleValue(), BigDecimal.ONE.doubleValue(), 0.0); + Assertions.assertEquals(data.getBigIntegerBoolean().intValue(), BigInteger.ONE.intValue(), 0.0); + Assertions.assertEquals(data.getBigIntegerNumber().intValue(), BigInteger.ONE.intValue(), 0.0); + Assertions.assertEquals(data.getBigIntegerString().intValue(), BigInteger.ONE.intValue(), 0.0); + Assertions.assertTrue(data.getBooleanBoolean()); + Assertions.assertTrue(data.getBooleanNumber()); + Assertions.assertTrue(data.getBooleanString()); + Assertions.assertEquals((long)data.getByteBoolean(), 1L); + Assertions.assertEquals((long)data.getByteNumber(), 1L); + Assertions.assertEquals((long)data.getByteString(), 1L); + try { + Assertions.assertEquals(data.getDateNumber(), DateUtils.parseDate("2020-01-01 01:01:01")); + Assertions.assertEquals(data.getDateString(), DateUtils.parseDate("2020-01-01 01:01:01")); + } catch (ParseException e) { + throw new ExcelCommonException("Test Exception", e); + } + Assertions.assertEquals(data.getLocalDateTimeNumber(), + DateUtils.parseLocalDateTime("2020-01-01 01:01:01", null, null)); + Assertions.assertEquals(data.getLocalDateTimeString(), + DateUtils.parseLocalDateTime("2020-01-01 01:01:01", null, null)); + Assertions.assertEquals(data.getDoubleBoolean(), 1.0, 0.0); + Assertions.assertEquals(data.getDoubleNumber(), 1.0, 0.0); + Assertions.assertEquals(data.getDoubleString(), 1.0, 0.0); + Assertions.assertEquals(data.getFloatBoolean(), (float)1.0, 0.0); + Assertions.assertEquals(data.getFloatNumber(), (float)1.0, 0.0); + Assertions.assertEquals(data.getFloatString(), (float)1.0, 0.0); + Assertions.assertEquals((long)data.getIntegerBoolean(), 1L); + Assertions.assertEquals((long)data.getIntegerNumber(), 1L); + Assertions.assertEquals((long)data.getIntegerString(), 1L); + Assertions.assertEquals((long)data.getLongBoolean(), 1L); + Assertions.assertEquals((long)data.getLongNumber(), 1L); + Assertions.assertEquals((long)data.getLongString(), 1L); + Assertions.assertEquals((long)data.getShortBoolean(), 1L); + Assertions.assertEquals((long)data.getShortNumber(), 1L); + Assertions.assertEquals((long)data.getShortString(), 1L); + Assertions.assertEquals(data.getStringBoolean().toLowerCase(), "true"); + Assertions.assertEquals(data.getStringString(), "测试"); + Assertions.assertEquals(data.getStringError(), "#VALUE!"); + if (context.readWorkbookHolder().getExcelType() != ExcelTypeEnum.CSV) { + Assertions.assertEquals("2020-1-1 1:01", data.getStringNumberDate()); + } else { + Assertions.assertEquals("2020-01-01 01:01:01", data.getStringNumberDate()); + } + double doubleStringFormulaNumber = new BigDecimal(data.getStringFormulaNumber()).doubleValue(); + Assertions.assertEquals(doubleStringFormulaNumber, 2.0, 0.0); + Assertions.assertEquals(data.getStringFormulaString(), "1测试"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatData.java new file mode 100644 index 0000000..d5617c5 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.core.dataformat; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class DateFormatData { + private String date; + private String dateStringCn; + private String dateStringCn2; + private String dateStringUs; + private String number; + private String numberStringCn; + private String numberStringUs; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatTest.java new file mode 100644 index 0000000..1730812 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/dataformat/DateFormatTest.java @@ -0,0 +1,104 @@ +package ai.chat2db.excel.test.core.dataformat; + +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class DateFormatTest { + + private static File file07V2; + private static File file07; + private static File file03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx"); + file03 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xls"); + file07V2 = TestFileUtil.readFile("dataformat" + File.separator + "dataformatv2.xlsx"); + + } + + @Test + public void t01Read07() { + readCn(file07); + readUs(file07); + } + + @Test + public void t02Read03() { + readCn(file03); + readUs(file03); + } + + @Test + public void t03Read() { + List> dataMap = EasyExcel.read(file07V2).headRowNumber(0).doReadAllSync(); + log.info("dataMap:{}", JSON.toJSONString(dataMap)); + Assertions.assertEquals("15:00", dataMap.get(0).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(1).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(2).get(0)); + Assertions.assertEquals("2023-1-01 00:00:01", dataMap.get(3).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(4).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(5).get(0)); + Assertions.assertEquals("2023-1-01 00:00:01", dataMap.get(6).get(0)); + } + + private void readCn(File file) { + List list = + EasyExcel.read(file, DateFormatData.class, null).locale(Locale.CHINA).sheet().doReadSync(); + for (DateFormatData data : list) { + if (!Objects.equals(data.getDateStringCn(), data.getDate()) && !Objects.equals(data.getDateStringCn2(), + data.getDate())) { + log.info("date:cn:{},{},{}", data.getDateStringCn(), data.getDateStringCn2(), data.getDate()); + } + if (data.getNumberStringCn() != null && !data.getNumberStringCn().equals(data.getNumber())) { + log.info("number:cn{},{}", data.getNumberStringCn(), data.getNumber()); + } + } + for (DateFormatData data : list) { + // The way dates are read in Chinese is different on Linux and Mac, so it is acceptable if it matches + // either one. + // For example, on Linux: 1-Jan -> 1-1月 + // On Mac: 1-Jan -> 1-一月 + Assertions.assertTrue( + Objects.equals(data.getDateStringCn(), data.getDate()) || Objects.equals(data.getDateStringCn2(), + data.getDate())); + Assertions.assertEquals(data.getNumberStringCn(), data.getNumber()); + } + } + + private void readUs(File file) { + List list = + EasyExcel.read(file, DateFormatData.class, null).locale(Locale.US).sheet().doReadSync(); + for (DateFormatData data : list) { + if (data.getDateStringUs() != null && !data.getDateStringUs().equals(data.getDate())) { + log.info("date:us:{},{}", data.getDateStringUs(), data.getDate()); + } + if (data.getNumberStringUs() != null && !data.getNumberStringUs().equals(data.getNumber())) { + log.info("number:us{},{}", data.getNumberStringUs(), data.getNumber()); + } + } + for (DateFormatData data : list) { + Assertions.assertEquals(data.getDateStringUs(), data.getDate()); + Assertions.assertEquals(data.getNumberStringUs(), data.getNumber()); + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptData.java new file mode 100644 index 0000000..3e6b3bf --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.encrypt; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class EncryptData { + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataListener.java new file mode 100644 index 0000000..75ed3e9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataListener.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.core.encrypt; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class EncryptDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(EncryptDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(EncryptData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 10); + Assertions.assertEquals(list.get(0).getName(), "姓名0"); + Assertions.assertEquals((int)(context.readSheetHolder().getSheetNo()), 0); + Assertions.assertEquals( + context.readSheetHolder().getExcelReadHeadProperty().getHeadMap().get(0).getHeadNameList().get(0), "姓名"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataTest.java new file mode 100644 index 0000000..bf6223d --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/encrypt/EncryptDataTest.java @@ -0,0 +1,95 @@ +package ai.chat2db.excel.test.core.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.core.simple.SimpleData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class EncryptDataTest { + + private static File file07; + private static File file03; + private static File file07OutputStream; + private static File file03OutputStream; + + @Test + public void testformat() { + DecimalFormat decimalFormat = new DecimalFormat("0.00"); + decimalFormat.setRoundingMode(RoundingMode.HALF_UP); + BigDecimal bigDecimal = new BigDecimal("0.105"); + + System.out.println(decimalFormat.format(bigDecimal)); + } + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("encrypt07.xlsx"); + file03 = TestFileUtil.createNewFile("encrypt03.xls"); + file07OutputStream = TestFileUtil.createNewFile("encryptOutputStream07.xlsx"); + file03OutputStream = TestFileUtil.createNewFile("encryptOutputStream03.xls"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteStream07() throws Exception { + readAndWriteStream(file07OutputStream, ExcelTypeEnum.XLSX); + } + + @Test + public void t04ReadAndWriteStream03() throws Exception { + readAndWriteStream(file03OutputStream, ExcelTypeEnum.XLS); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, EncryptData.class).password("123456").sheet().doWrite(data()); + EasyExcel.read(file, EncryptData.class, new EncryptDataListener()).password("123456").sheet().doRead(); + } + + private void readAndWriteStream(File file, ExcelTypeEnum excelType) throws Exception { + FileOutputStream fileOutputStream = new FileOutputStream(file); + EasyExcel.write(fileOutputStream, EncryptData.class).password("123456").excelType(excelType).sheet() + .doWrite(data()); + fileOutputStream.close(); + + FileInputStream fileInputStream = new FileInputStream(file); + EasyExcel.read(fileInputStream, EncryptData.class, new EncryptDataListener()).password("123456").sheet() + .doRead(); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + SimpleData simpleData = new SimpleData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExcelAnalysisStopSheetExceptionDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExcelAnalysisStopSheetExceptionDataListener.java new file mode 100644 index 0000000..f93b728 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExcelAnalysisStopSheetExceptionDataListener.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.test.core.exception; + +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelAnalysisStopSheetException; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Slf4j +public class ExcelAnalysisStopSheetExceptionDataListener extends AnalysisEventListener { + + private Map> dataMap = MapUtils.newHashMap(); + + + @Override + public void invoke(ExceptionData data, AnalysisContext context) { + List sheetDataList = dataMap.computeIfAbsent(context.readSheetHolder().getSheetNo(), + key -> ListUtils.newArrayList()); + sheetDataList.add(data.getName()); + if (sheetDataList.size() >= 5) { + throw new ExcelAnalysisStopSheetException(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + List sheetDataList = dataMap.get(context.readSheetHolder().getSheetNo()); + Assertions.assertNotNull(sheetDataList); + Assertions.assertEquals(5, sheetDataList.size()); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionData.java new file mode 100644 index 0000000..d411220 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.exception; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExceptionData { + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataListener.java new file mode 100644 index 0000000..efa0a88 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataListener.java @@ -0,0 +1,48 @@ +package ai.chat2db.excel.test.core.exception; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ExceptionDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionData.class); + List list = new ArrayList(); + + @Override + public void onException(Exception exception, AnalysisContext context) { + LOGGER.info("抛出异常,忽略:{}", exception.getMessage(), exception); + } + + @Override + public boolean hasNext(AnalysisContext context) { + return list.size() != 8; + } + + @Override + public void invoke(ExceptionData data, AnalysisContext context) { + list.add(data); + if (list.size() == 5) { + int i = 5 / 0; + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 8); + Assertions.assertEquals(list.get(0).getName(), "姓名0"); + Assertions.assertEquals((int)(context.readSheetHolder().getSheetNo()), 0); + Assertions.assertEquals( + context.readSheetHolder().getExcelReadHeadProperty().getHeadMap().get(0).getHeadNameList().get(0), "姓名"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataTest.java new file mode 100644 index 0000000..ab85890 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionDataTest.java @@ -0,0 +1,143 @@ +package ai.chat2db.excel.test.core.exception; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ExceptionDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File fileExcelAnalysisStopSheetException07; + private static File fileExcelAnalysisStopSheetException03; + private static File fileExcelAnalysisStopSheetExceptionCsv; + private static File fileException07; + private static File fileException03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("exception.xlsx"); + file03 = TestFileUtil.createNewFile("exception.xls"); + fileCsv = TestFileUtil.createNewFile("exception.csv"); + fileExcelAnalysisStopSheetException07 = TestFileUtil.createNewFile("excelAnalysisStopSheetException.xlsx"); + fileExcelAnalysisStopSheetException03 = TestFileUtil.createNewFile("excelAnalysisStopSheetException.xls"); + fileException07 = TestFileUtil.createNewFile("exceptionThrow.xlsx"); + fileException03 = TestFileUtil.createNewFile("exceptionThrow.xls"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + @Test + public void t11ReadAndWrite07() throws Exception { + readAndWriteException(fileException07); + } + + @Test + public void t12ReadAndWrite03() throws Exception { + readAndWriteException(fileException03); + } + + @Test + public void t21ReadAndWrite07() throws Exception { + readAndWriteExcelAnalysisStopSheetException(fileExcelAnalysisStopSheetException07); + } + + @Test + public void t22ReadAndWrite03() throws Exception { + readAndWriteExcelAnalysisStopSheetException(fileExcelAnalysisStopSheetException03); + } + + + private void readAndWriteExcelAnalysisStopSheetException(File file) throws Exception { + try (ExcelWriter excelWriter = EasyExcel.write(file, ExceptionData.class).build()) { + for (int i = 0; i < 5; i++) { + String sheetName = "sheet" + i; + WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build(); + List data = data(sheetName); + excelWriter.write(data, writeSheet); + } + } + + ExcelAnalysisStopSheetExceptionDataListener excelAnalysisStopSheetExceptionDataListener + = new ExcelAnalysisStopSheetExceptionDataListener(); + EasyExcel.read(file, ExceptionData.class, excelAnalysisStopSheetExceptionDataListener).doReadAll(); + Map> dataMap = excelAnalysisStopSheetExceptionDataListener.getDataMap(); + Assertions.assertEquals(5, dataMap.size()); + for (int i = 0; i < 5; i++) { + List sheetDataList = dataMap.get(i); + Assertions.assertNotNull(sheetDataList); + Assertions.assertEquals(5, sheetDataList.size()); + String sheetName = "sheet" + i; + + for (String sheetData : sheetDataList) { + Assertions.assertTrue(sheetData.startsWith(sheetName)); + } + } + } + + private void readAndWriteException(File file) throws Exception { + EasyExcel.write(new FileOutputStream(file), ExceptionData.class).sheet().doWrite(data()); + ArithmeticException exception = Assertions.assertThrows(ArithmeticException.class, () -> EasyExcel.read( + new FileInputStream(file), ExceptionData.class, + new ExceptionThrowDataListener()).sheet().doRead()); + Assertions.assertEquals("/ by zero", exception.getMessage()); + } + + private void readAndWrite(File file) throws Exception { + EasyExcel.write(new FileOutputStream(file), ExceptionData.class).sheet().doWrite(data()); + EasyExcel.read(new FileInputStream(file), ExceptionData.class, new ExceptionDataListener()).sheet().doRead(); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + ExceptionData simpleData = new ExceptionData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } + + private List data(String prefix) { + List list = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + ExceptionData simpleData = new ExceptionData(); + simpleData.setName(prefix + "-姓名" + i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionThrowDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionThrowDataListener.java new file mode 100644 index 0000000..7b9bb58 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/exception/ExceptionThrowDataListener.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.test.core.exception; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.context.AnalysisContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ExceptionThrowDataListener implements ReadListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionData.class); + List list = new ArrayList(); + + @Override + public void invoke(ExceptionData data, AnalysisContext context) { + list.add(data); + if (list.size() == 5) { + int i = 5 / 0; + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeData.java new file mode 100644 index 0000000..1179f42 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeData.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.test.core.excludeorinclude; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExcludeOrIncludeData { + @ExcelProperty(order = 1) + private String column1; + @ExcelProperty(order = 2) + private String column2; + @ExcelProperty(order = 3) + private String column3; + @ExcelProperty(order = 4) + private String column4; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeDataTest.java new file mode 100644 index 0000000..030f93b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/excludeorinclude/ExcludeOrIncludeDataTest.java @@ -0,0 +1,259 @@ +package ai.chat2db.excel.test.core.excludeorinclude; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import org.junit.jupiter.api.*; + +import java.io.File; +import java.util.*; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ExcludeOrIncludeDataTest { + + private static File excludeIndex07; + private static File excludeIndex03; + private static File excludeIndexCsv; + private static File excludeFieldName07; + private static File excludeFieldName03; + private static File excludeFieldNameCsv; + private static File includeIndex07; + private static File includeIndex03; + private static File includeIndexCsv; + private static File includeFieldName07; + private static File includeFieldName03; + private static File includeFieldNameCsv; + private static File includeFieldNameOrder07; + private static File includeFieldNameOrder03; + private static File includeFieldNameOrderCsv; + + private static File includeFieldNameOrderIndex07; + private static File includeFieldNameOrderIndex03; + private static File includeFieldNameOrderIndexCsv; + + @BeforeAll + public static void init() { + excludeIndex07 = TestFileUtil.createNewFile("excludeIndex.xlsx"); + excludeIndex03 = TestFileUtil.createNewFile("excludeIndex.xls"); + excludeIndexCsv = TestFileUtil.createNewFile("excludeIndex.csv"); + excludeFieldName07 = TestFileUtil.createNewFile("excludeFieldName.xlsx"); + excludeFieldName03 = TestFileUtil.createNewFile("excludeFieldName.xls"); + excludeFieldNameCsv = TestFileUtil.createNewFile("excludeFieldName.csv"); + includeIndex07 = TestFileUtil.createNewFile("includeIndex.xlsx"); + includeIndex03 = TestFileUtil.createNewFile("includeIndex.xls"); + includeIndexCsv = TestFileUtil.createNewFile("includeIndex.csv"); + includeFieldName07 = TestFileUtil.createNewFile("includeFieldName.xlsx"); + includeFieldName03 = TestFileUtil.createNewFile("includeFieldName.xls"); + includeFieldNameCsv = TestFileUtil.createNewFile("includeFieldName.csv"); + includeFieldNameOrder07 = TestFileUtil.createNewFile("includeFieldNameOrder.xlsx"); + includeFieldNameOrder03 = TestFileUtil.createNewFile("includeFieldNameOrder.xls"); + includeFieldNameOrderCsv = TestFileUtil.createNewFile("includeFieldNameOrder.csv"); + includeFieldNameOrderIndex07 = TestFileUtil.createNewFile("includeFieldNameOrderIndex.xlsx"); + includeFieldNameOrderIndex03 = TestFileUtil.createNewFile("includeFieldNameOrderIndex.xls"); + includeFieldNameOrderIndexCsv = TestFileUtil.createNewFile("includeFieldNameOrderIndex.csv"); + } + + @Test + public void t01ExcludeIndex07() { + excludeIndex(excludeIndex07); + } + + @Test + public void t02ExcludeIndex03() { + excludeIndex(excludeIndex03); + } + + @Test + public void t03ExcludeIndexCsv() { + excludeIndex(excludeIndexCsv); + } + + @Test + public void t11ExcludeFieldName07() { + excludeFieldName(excludeFieldName07); + } + + @Test + public void t12ExcludeFieldName03() { + excludeFieldName(excludeFieldName03); + } + + @Test + public void t13ExcludeFieldNameCsv() { + excludeFieldName(excludeFieldNameCsv); + } + + @Test + public void t21IncludeIndex07() { + includeIndex(includeIndex07); + } + + @Test + public void t22IncludeIndex03() { + includeIndex(includeIndex03); + } + + @Test + public void t23IncludeIndexCsv() { + includeIndex(includeIndexCsv); + } + + @Test + public void t31IncludeFieldName07() { + includeFieldName(includeFieldName07); + } + + @Test + public void t32IncludeFieldName03() { + includeFieldName(includeFieldName03); + } + + @Test + public void t33IncludeFieldNameCsv() { + includeFieldName(includeFieldNameCsv); + } + + @Test + public void t41IncludeFieldNameOrder07() { + includeFieldNameOrder(includeFieldNameOrder07); + } + + @Test + public void t42IncludeFieldNameOrder03() { + includeFieldNameOrder(includeFieldNameOrder03); + } + + @Test + public void t43IncludeFieldNameOrderCsv() { + includeFieldNameOrder(includeFieldNameOrderCsv); + } + + @Test + public void t41IncludeFieldNameOrderIndex07() { + includeFieldNameOrderIndex(includeFieldNameOrderIndex07); + } + + @Test + public void t42IncludeFieldNameOrderIndex03() { + includeFieldNameOrderIndex(includeFieldNameOrderIndex03); + } + + @Test + public void t43IncludeFieldNameOrderIndexCsv() { + includeFieldNameOrderIndex(includeFieldNameOrderIndexCsv); + } + + private void excludeIndex(File file) { + Set excludeColumnIndexes = new HashSet(); + excludeColumnIndexes.add(0); + excludeColumnIndexes.add(3); + EasyExcel.write(file, ExcludeOrIncludeData.class).excludeColumnIndexes(excludeColumnIndexes).sheet() + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(2, record.size()); + Assertions.assertEquals("column2", record.get(0)); + Assertions.assertEquals("column3", record.get(1)); + + } + + private void excludeFieldName(File file) { + Set excludeColumnFieldNames = new HashSet(); + excludeColumnFieldNames.add("column1"); + excludeColumnFieldNames.add("column3"); + excludeColumnFieldNames.add("column4"); + EasyExcel.write(file, ExcludeOrIncludeData.class).excludeColumnFieldNames(excludeColumnFieldNames).sheet() + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(1, record.size()); + Assertions.assertEquals("column2", record.get(0)); + + } + + private void includeIndex(File file) { + Set includeColumnIndexes = new HashSet(); + includeColumnIndexes.add(1); + includeColumnIndexes.add(2); + EasyExcel.write(file, ExcludeOrIncludeData.class).includeColumnIndexes(includeColumnIndexes).sheet() + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(2, record.size()); + Assertions.assertEquals("column2", record.get(0)); + Assertions.assertEquals("column3", record.get(1)); + + } + + private void includeFieldName(File file) { + Set includeColumnFieldNames = new HashSet(); + includeColumnFieldNames.add("column2"); + includeColumnFieldNames.add("column3"); + EasyExcel.write(file, ExcludeOrIncludeData.class) + .sheet() + .includeColumnFieldNames(includeColumnFieldNames) + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(2, record.size()); + Assertions.assertEquals("column2", record.get(0)); + Assertions.assertEquals("column3", record.get(1)); + } + + private void includeFieldNameOrderIndex(File file) { + List includeColumnIndexes = new ArrayList<>(); + includeColumnIndexes.add(3); + includeColumnIndexes.add(1); + includeColumnIndexes.add(2); + includeColumnIndexes.add(0); + EasyExcel.write(file, ExcludeOrIncludeData.class) + .includeColumnIndexes(includeColumnIndexes) + .orderByIncludeColumn(true). + sheet() + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(4, record.size()); + Assertions.assertEquals("column4", record.get(0)); + Assertions.assertEquals("column2", record.get(1)); + Assertions.assertEquals("column3", record.get(2)); + Assertions.assertEquals("column1", record.get(3)); + } + + private void includeFieldNameOrder(File file) { + List includeColumnFieldNames = new ArrayList<>(); + includeColumnFieldNames.add("column4"); + includeColumnFieldNames.add("column2"); + includeColumnFieldNames.add("column3"); + EasyExcel.write(file, ExcludeOrIncludeData.class) + .includeColumnFieldNames(includeColumnFieldNames) + .orderByIncludeColumn(true). + sheet() + .doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals(3, record.size()); + Assertions.assertEquals("column4", record.get(0)); + Assertions.assertEquals("column2", record.get(1)); + Assertions.assertEquals("column3", record.get(2)); + } + + private List data() { + List list = new ArrayList(); + ExcludeOrIncludeData excludeOrIncludeData = new ExcludeOrIncludeData(); + excludeOrIncludeData.setColumn1("column1"); + excludeOrIncludeData.setColumn2("column2"); + excludeOrIncludeData.setColumn3("column3"); + excludeOrIncludeData.setColumn4("column4"); + list.add(excludeOrIncludeData); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraData.java new file mode 100644 index 0000000..f89ac32 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.extra; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ExtraData { + + private String row1; + + private String row2; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataListener.java new file mode 100644 index 0000000..216805f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataListener.java @@ -0,0 +1,55 @@ +package ai.chat2db.excel.test.core.extra; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.CellExtra; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ExtraDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ExtraData.class); + + @Override + public void invoke(ExtraData data, AnalysisContext context) {} + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + + @Override + public void extra(CellExtra extra, AnalysisContext context) { + LOGGER.info("extra data:{}", JSON.toJSONString(extra)); + switch (extra.getType()) { + case COMMENT: + Assertions.assertEquals("批注的内容", extra.getText()); + Assertions.assertEquals(4, (int)extra.getRowIndex()); + Assertions.assertEquals(0, (int)extra.getColumnIndex()); + break; + case HYPERLINK: + if ("Sheet1!A1".equals(extra.getText())) { + Assertions.assertEquals(1, (int)extra.getRowIndex()); + Assertions.assertEquals(0, (int)extra.getColumnIndex()); + } else if ("Sheet2!A1".equals(extra.getText())) { + Assertions.assertEquals(2, (int)extra.getFirstRowIndex()); + Assertions.assertEquals(0, (int)extra.getFirstColumnIndex()); + Assertions.assertEquals(3, (int)extra.getLastRowIndex()); + Assertions.assertEquals(1, (int)extra.getLastColumnIndex()); + } else { + Assertions.fail("Unknown hyperlink!"); + } + break; + case MERGE: + Assertions.assertEquals(5, (int)extra.getFirstRowIndex()); + Assertions.assertEquals(0, (int)extra.getFirstColumnIndex()); + Assertions.assertEquals(6, (int)extra.getLastRowIndex()); + Assertions.assertEquals(1, (int)extra.getLastColumnIndex()); + break; + default: + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataTest.java new file mode 100644 index 0000000..2b2b80f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/extra/ExtraDataTest.java @@ -0,0 +1,86 @@ +package ai.chat2db.excel.test.core.extra; + +import java.io.File; + +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.CellExtra; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ExtraDataTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ExtraDataTest.class); + private static File file03; + private static File file07; + + private static File extraRelationships; + + @BeforeAll + public static void init() { + file03 = TestFileUtil.readFile("extra" + File.separator + "extra.xls"); + file07 = TestFileUtil.readFile("extra" + File.separator + "extra.xlsx"); + extraRelationships = TestFileUtil.readFile("extra" + File.separator + "extraRelationships.xlsx"); + } + + @Test + public void t01Read07() { + read(file07); + } + + @Test + public void t02Read03() { + read(file03); + } + + @Test + public void t03Read() { + EasyExcel.read(extraRelationships, ExtraData.class, new ReadListener() { + @Override + public void invoke(Object data, AnalysisContext context) { + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } + + @Override + public void extra(CellExtra extra, AnalysisContext context) { + LOGGER.info("extra data:{}", JSON.toJSONString(extra)); + switch (extra.getType()) { + case HYPERLINK: + if ("222222222".equals(extra.getText())) { + Assertions.assertEquals(1, (int)extra.getRowIndex()); + Assertions.assertEquals(0, (int)extra.getColumnIndex()); + } else if ("333333333333".equals(extra.getText())) { + Assertions.assertEquals(1, (int)extra.getRowIndex()); + Assertions.assertEquals(1, (int)extra.getColumnIndex()); + } else { + Assertions.fail("Unknown hyperlink!"); + } + break; + default: + } + } + }) + .extraRead(CellExtraTypeEnum.HYPERLINK) + .sheet() + .doRead(); + } + + private void read(File file) { + EasyExcel.read(file, ExtraData.class, new ExtraDataListener()).extraRead(CellExtraTypeEnum.COMMENT) + .extraRead(CellExtraTypeEnum.HYPERLINK).extraRead(CellExtraTypeEnum.MERGE).sheet().doRead(); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillData.java new file mode 100644 index 0000000..f6f93a2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillData.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.test.core.fill; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.converters.doubleconverter.DoubleStringConverter; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FillData { + private String name; + @NumberFormat("#") + @ExcelProperty(converter = DoubleStringConverter.class) + private Double number; + private String empty; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillDataTest.java new file mode 100644 index 0000000..c298cb8 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/FillDataTest.java @@ -0,0 +1,225 @@ +package ai.chat2db.excel.test.core.fill; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.WriteDirectionEnum; +import ai.chat2db.excel.write.merge.LoopMergeStrategy; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.fill.FillConfig; +import ai.chat2db.excel.write.metadata.fill.FillWrapper; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class FillDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File simpleTemplate07; + private static File simpleTemplate03; + private static File simpleTemplateCsv; + private static File fileComplex07; + private static File complexFillTemplate07; + private static File fileComplex03; + private static File complexFillTemplate03; + private static File fileHorizontal07; + private static File horizontalFillTemplate07; + private static File fileHorizontal03; + private static File horizontalFillTemplate03; + private static File byName07; + private static File byName03; + private static File byNameTemplate07; + private static File byNameTemplate03; + private static File fileComposite07; + private static File compositeFillTemplate07; + private static File fileComposite03; + private static File compositeFillTemplate03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("fill07.xlsx"); + file03 = TestFileUtil.createNewFile("fill03.xls"); + fileCsv = TestFileUtil.createNewFile("fill.csv"); + simpleTemplate07 = TestFileUtil.readFile("fill" + File.separator + "simple.xlsx"); + simpleTemplate03 = TestFileUtil.readFile("fill" + File.separator + "simple.xls"); + simpleTemplateCsv = TestFileUtil.readFile("fill" + File.separator + "simple.csv"); + fileComplex07 = TestFileUtil.createNewFile("fillComplex07.xlsx"); + complexFillTemplate07 = TestFileUtil.readFile("fill" + File.separator + "complex.xlsx"); + fileComplex03 = TestFileUtil.createNewFile("fillComplex03.xls"); + complexFillTemplate03 = TestFileUtil.readFile("fill" + File.separator + "complex.xls"); + fileHorizontal07 = TestFileUtil.createNewFile("fillHorizontal07.xlsx"); + horizontalFillTemplate07 = TestFileUtil.readFile("fill" + File.separator + "horizontal.xlsx"); + fileHorizontal03 = TestFileUtil.createNewFile("fillHorizontal03.xls"); + horizontalFillTemplate03 = TestFileUtil.readFile("fill" + File.separator + "horizontal.xls"); + byName07 = TestFileUtil.createNewFile("byName07.xlsx"); + byNameTemplate07 = TestFileUtil.readFile("fill" + File.separator + "byName.xlsx"); + byName03 = TestFileUtil.createNewFile("byName03.xls"); + byNameTemplate03 = TestFileUtil.readFile("fill" + File.separator + "byName.xls"); + fileComposite07 = TestFileUtil.createNewFile("fileComposite07.xlsx"); + compositeFillTemplate07 = TestFileUtil.readFile("fill" + File.separator + "composite.xlsx"); + fileComposite03 = TestFileUtil.createNewFile("fileComposite03.xls"); + compositeFillTemplate03 = TestFileUtil.readFile("fill" + File.separator + "composite.xls"); + } + + @Test + public void t01Fill07() { + fill(file07, simpleTemplate07); + } + + @Test + public void t02Fill03() { + fill(file03, simpleTemplate03); + } + + @Test + public void t03FillCsv() { + ExcelGenerateException excelGenerateException = Assertions.assertThrows(ExcelGenerateException.class, + () -> fill(fileCsv, simpleTemplateCsv)); + Assertions.assertEquals("csv cannot use template.", excelGenerateException.getMessage()); + } + + @Test + public void t03ComplexFill07() { + complexFill(fileComplex07, complexFillTemplate07); + } + + @Test + public void t04ComplexFill03() { + complexFill(fileComplex03, complexFillTemplate03); + } + + @Test + public void t05HorizontalFill07() { + horizontalFill(fileHorizontal07, horizontalFillTemplate07); + } + + @Test + public void t06HorizontalFill03() { + horizontalFill(fileHorizontal03, horizontalFillTemplate03); + } + + @Test + public void t07ByNameFill07() { + byNameFill(byName07, byNameTemplate07); + } + + @Test + public void t08ByNameFill03() { + byNameFill(byName03, byNameTemplate03); + } + + @Test + public void t09CompositeFill07() { + compositeFill(fileComposite07, compositeFillTemplate07); + } + + @Test + public void t10CompositeFill03() { + compositeFill(fileComposite03, compositeFillTemplate03); + } + + private void byNameFill(File file, File template) { + FillData fillData = new FillData(); + fillData.setName("张三"); + fillData.setNumber(5.2); + EasyExcel.write(file, FillData.class).withTemplate(template).sheet("Sheet2").doFill(fillData); + } + + private void compositeFill(File file, File template) { + try (ExcelWriter excelWriter = EasyExcel.write(file).withTemplate(template).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + } + + List list = EasyExcel.read(file).ignoreEmptyRow(false).sheet().headRowNumber(0).doReadSync(); + Map map0 = (Map)list.get(0); + Assertions.assertEquals("张三", map0.get(21)); + Map map27 = (Map)list.get(27); + Assertions.assertEquals("张三", map27.get(0)); + Map map29 = (Map)list.get(29); + Assertions.assertEquals("张三", map29.get(3)); + } + + private void horizontalFill(File file, File template) { + try (ExcelWriter excelWriter = EasyExcel.write(file).withTemplate(template).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + excelWriter.finish(); + } + + List list = EasyExcel.read(file).sheet().headRowNumber(0).doReadSync(); + Assertions.assertEquals(list.size(), 5L); + Map map0 = (Map)list.get(0); + Assertions.assertEquals("张三", map0.get(2)); + } + + private void complexFill(File file, File template) { + try (ExcelWriter excelWriter = EasyExcel.write(file).withTemplate(template).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().registerWriteHandler(new LoopMergeStrategy(2, 0)).build(); + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet); + } + List list = EasyExcel.read(file).sheet().headRowNumber(3).doReadSync(); + Assertions.assertEquals(list.size(), 21L); + Map map19 = (Map)list.get(19); + Assertions.assertEquals("张三", map19.get(0)); + } + + private void fill(File file, File template) { + FillData fillData = new FillData(); + fillData.setName("张三"); + fillData.setNumber(5.2); + EasyExcel.write(file, FillData.class).withTemplate(template).sheet().doFill(fillData); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + if (i == 5) { + fillData.setName(null); + } + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationData.java new file mode 100644 index 0000000..8873175 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationData.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.core.fill.annotation; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.annotation.write.style.ContentLoopMerge; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.converters.string.StringImageConverter; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(100) +public class FillAnnotationData { + @ExcelProperty("日期") + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + private Date date; + + @ExcelProperty(value = "数字") + @NumberFormat("#.##%") + private Double number; + + @ContentLoopMerge(columnExtend = 2) + @ExcelProperty("字符串1") + private String string1; + @ExcelProperty("字符串2") + private String string2; + @ExcelProperty(value = "图片", converter = StringImageConverter.class) + private String image; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationDataTest.java new file mode 100644 index 0000000..18b7239 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/annotation/FillAnnotationDataTest.java @@ -0,0 +1,122 @@ +package ai.chat2db.excel.test.core.fill.annotation; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class FillAnnotationDataTest { + + private static File file07; + private static File file03; + private static File fileTemplate07; + private static File fileTemplate03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("fillAnnotation07.xlsx"); + file03 = TestFileUtil.createNewFile("fillAnnotation03.xls"); + fileTemplate07 = TestFileUtil.readFile("fill" + File.separator + "annotation.xlsx"); + fileTemplate03 = TestFileUtil.readFile("fill" + File.separator + "annotation.xls"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07, fileTemplate07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03, fileTemplate03); + } + + private void readAndWrite(File file, File fileTemplate) throws Exception { + EasyExcel.write().file(file).head(FillAnnotationData.class).withTemplate(fileTemplate).sheet().doFill(data()); + + try (Workbook workbook = WorkbookFactory.create(file)) { + Sheet sheet = workbook.getSheetAt(0); + + Row row1 = sheet.getRow(1); + Assertions.assertEquals(2000, row1.getHeight(), 0); + Cell cell10 = row1.getCell(0); + Date date = cell10.getDateCellValue(); + Assertions.assertEquals(DateUtils.parseDate("2020-01-01 01:01:01").getTime(), date.getTime()); + String dataFormatString = cell10.getCellStyle().getDataFormatString(); + Assertions.assertEquals("yyyy年MM月dd日HH时mm分ss秒", dataFormatString); + Cell cell11 = row1.getCell(1); + Assertions.assertEquals(99.99, cell11.getNumericCellValue(), 2); + boolean hasMerge = false; + for (CellRangeAddress mergedRegion : sheet.getMergedRegions()) { + if (mergedRegion.getFirstRow() == 1 && mergedRegion.getLastRow() == 1 + && mergedRegion.getFirstColumn() == 2 && mergedRegion.getLastColumn() == 3) { + hasMerge = true; + break; + } + } + Assertions.assertTrue(hasMerge); + if (sheet instanceof XSSFSheet) { + XSSFSheet xssfSheet = (XSSFSheet)sheet; + List shapeList = xssfSheet.getDrawingPatriarch().getShapes(); + XSSFShape shape0 = shapeList.get(0); + Assertions.assertTrue(shape0 instanceof XSSFPicture); + XSSFPicture picture0 = (XSSFPicture)shape0; + CTMarker ctMarker0 = picture0.getPreferredSize().getFrom(); + Assertions.assertEquals(1, ctMarker0.getRow()); + Assertions.assertEquals(4, ctMarker0.getCol()); + } else { + HSSFSheet hssfSheet = (HSSFSheet)sheet; + List shapeList = hssfSheet.getDrawingPatriarch().getChildren(); + HSSFShape shape0 = shapeList.get(0); + Assertions.assertTrue(shape0 instanceof HSSFPicture); + HSSFPicture picture0 = (HSSFPicture)shape0; + HSSFClientAnchor anchor = (HSSFClientAnchor)picture0.getAnchor(); + Assertions.assertEquals(1, anchor.getRow1()); + Assertions.assertEquals(4, anchor.getCol1()); + } + } + } + + private List data() throws Exception { + List list = new ArrayList<>(); + FillAnnotationData data = new FillAnnotationData(); + data.setDate(DateUtils.parseDate("2020-01-01 01:01:01")); + data.setNumber(99.99); + data.setString1("string1"); + data.setString2("string2"); + data.setImage(TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"); + list.add(data); + list.add(data); + list.add(data); + list.add(data); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedData.java new file mode 100644 index 0000000..eec67be --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedData.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.core.fill.style; + +import java.util.Date; + +import ai.chat2db.excel.enums.BooleanEnum; +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; +import ai.chat2db.excel.annotation.write.style.ContentFontStyle; +import ai.chat2db.excel.annotation.write.style.ContentStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FillStyleAnnotatedData { + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 13) + @ContentFontStyle(bold = BooleanEnum.TRUE, color = 19) + private String name; + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10) + @ContentFontStyle(bold = BooleanEnum.TRUE, color = 16) + private Double number; + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17) + @ContentFontStyle(bold = BooleanEnum.TRUE, color = 58) + private Date date; + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 12) + @ContentFontStyle(bold = BooleanEnum.TRUE, color = 18) + private String empty; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedTest.java new file mode 100644 index 0000000..e525710 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleAnnotatedTest.java @@ -0,0 +1,329 @@ +package ai.chat2db.excel.test.core.fill.style; + +import java.io.File; +import java.io.FileInputStream; +import java.util.List; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.style.AbstractVerticalCellStyleStrategy; +import ai.chat2db.excel.test.core.fill.FillData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class FillStyleAnnotatedTest { + + private static File FillStyleAnnotated07; + private static File FillStyleAnnotated03; + private static File fileStyleTemplate07; + private static File fileStyleTemplate03; + + @BeforeAll + public static void init() { + FillStyleAnnotated07 = TestFileUtil.createNewFile("FillStyleAnnotated07.xlsx"); + FillStyleAnnotated03 = TestFileUtil.createNewFile("FillStyleAnnotated03.xls"); + fileStyleTemplate07 = TestFileUtil.readFile("fill" + File.separator + "style.xlsx"); + fileStyleTemplate03 = TestFileUtil.readFile("fill" + File.separator + "style.xls"); + } + + @Test + public void t01Fill07() throws Exception { + fill(FillStyleAnnotated07, fileStyleTemplate07); + XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(FillStyleAnnotated07)); + XSSFSheet sheet = workbook.getSheetAt(0); + t01Fill07check(sheet.getRow(1)); + t01Fill07check(sheet.getRow(2)); + } + + private void t01Fill07check(XSSFRow row) { + XSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFFFF00", cell0.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF808000", cell0.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell0.getCellStyle().getFont().getBold()); + + XSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell1.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF800000", cell1.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell1.getCellStyle().getFont().getBold()); + + XSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("FF008000", cell2.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF003300", cell2.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell2.getCellStyle().getFont().getBold()); + + XSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell3.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FFEEECE1", cell3.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell3.getCellStyle().getFont().getBold()); + + XSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFC00000", cell4.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF000000", cell4.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell4.getCellStyle().getFont().getBold()); + + XSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFF79646", cell5.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF8064A2", cell5.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell5.getCellStyle().getFont().getBold()); + } + + @Test + public void t02Fill03() throws Exception { + fill(FillStyleAnnotated03, fileStyleTemplate03); + HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(FillStyleAnnotated03)); + HSSFSheet sheet = workbook.getSheetAt(0); + t02Fill03check(workbook, sheet.getRow(1)); + t02Fill03check(workbook, sheet.getRow(2)); + } + + private void t02Fill03check(HSSFWorkbook workbook, HSSFRow row) { + HSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:FFFF:0", cell0.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:8080:0", cell0.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell0.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell1.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:0:0", cell1.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell1.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("0:8080:0", cell2.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:3333:0", cell2.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell2.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell3.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("FFFF:FFFF:9999", cell3.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell3.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell4.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("3333:3333:3333", cell4.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell4.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell5.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("CCCC:9999:FFFF", cell5.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell5.getCellStyle().getFont(workbook).getBold()); + } + + private void fill(File file, File template) throws Exception { + EasyExcel.write(file, FillStyleAnnotatedData.class).withTemplate(template).sheet().doFill(data()); + } + + private void t11FillStyleHandler07check(XSSFRow row) { + XSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFFFF00", cell0.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF808000", cell0.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell0.getCellStyle().getFont().getBold()); + + XSSFCell cell1 = row.getCell(1); + Assertions.assertEquals("5", cell1.getStringCellValue()); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell1.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF800000", cell1.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell1.getCellStyle().getFont().getBold()); + + XSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("FF008000", cell2.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF003300", cell2.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell2.getCellStyle().getFont().getBold()); + + XSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FF0000FF", cell3.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF000080", cell3.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell3.getCellStyle().getFont().getBold()); + + XSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFFFF00", cell4.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF808000", cell4.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell4.getCellStyle().getFont().getBold()); + + XSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("FF008080", cell5.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF003366", cell5.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell5.getCellStyle().getFont().getBold()); + } + + private void t12FillStyleHandler03check(HSSFWorkbook workbook, HSSFRow row) { + HSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:FFFF:0", cell0.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:8080:0", cell0.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell0.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell1 = row.getCell(1); + Assertions.assertEquals("5", cell1.getStringCellValue()); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell1.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:0:0", cell1.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell1.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("0:8080:0", cell2.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:3333:0", cell2.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell2.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("0:0:FFFF", cell3.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:0:8080", cell3.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell3.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:FFFF:0", cell4.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:8080:0", cell4.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell4.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("0:8080:8080", cell5.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:3333:6666", cell5.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell5.getCellStyle().getFont(workbook).getBold()); + } + + private void fillStyleHandler(File file, File template) throws Exception { + EasyExcel.write(file, FillData.class).withTemplate(template).sheet() + .registerWriteHandler(new AbstractVerticalCellStyleStrategy() { + + @Override + protected WriteCellStyle contentCellStyle(CellWriteHandlerContext context) { + WriteCellStyle writeCellStyle = new WriteCellStyle(); + WriteFont writeFont = new WriteFont(); + writeCellStyle.setWriteFont(writeFont); + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + writeFont.setBold(true); + if (context.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + writeFont.setColor(IndexedColors.DARK_YELLOW.getIndex()); + } + if (context.getColumnIndex() == 1) { + writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + writeFont.setColor(IndexedColors.DARK_RED.getIndex()); + } + if (context.getColumnIndex() == 2) { + writeCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + writeFont.setColor(IndexedColors.DARK_GREEN.getIndex()); + } + if (context.getColumnIndex() == 3) { + writeCellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + writeFont.setColor(IndexedColors.DARK_BLUE.getIndex()); + } + if (context.getColumnIndex() == 4) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + writeFont.setColor(IndexedColors.DARK_YELLOW.getIndex()); + } + if (context.getColumnIndex() == 5) { + writeCellStyle.setFillForegroundColor(IndexedColors.TEAL.getIndex()); + writeFont.setColor(IndexedColors.DARK_TEAL.getIndex()); + } + return writeCellStyle; + } + + @Override + protected WriteCellStyle headCellStyle(Head head) { + return null; + } + + }) + .doFill(data()); + } + + private List data() throws Exception { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + FillStyleAnnotatedData fillData = new FillStyleAnnotatedData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + fillData.setDate(DateUtils.parseDate("2020-01-01 01:01:01")); + if (i == 5) { + fillData.setName(null); + } + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleData.java new file mode 100644 index 0000000..43f02e5 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleData.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.core.fill.style; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FillStyleData { + private String name; + private Double number; + private Date date; + private String empty; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleDataTest.java new file mode 100644 index 0000000..ef09ed2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/fill/style/FillStyleDataTest.java @@ -0,0 +1,350 @@ +package ai.chat2db.excel.test.core.fill.style; + +import java.io.File; +import java.io.FileInputStream; +import java.util.List; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.style.AbstractVerticalCellStyleStrategy; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class FillStyleDataTest { + + private static File fileStyle07; + private static File fileStyle03; + private static File fileStyleHandler07; + private static File fileStyleHandler03; + private static File fileStyleTemplate07; + private static File fileStyleTemplate03; + + @BeforeAll + public static void init() { + fileStyle07 = TestFileUtil.createNewFile("fileStyle07.xlsx"); + fileStyle03 = TestFileUtil.createNewFile("fileStyle03.xls"); + fileStyleHandler07 = TestFileUtil.createNewFile("fileStyleHandler07.xlsx"); + fileStyleHandler03 = TestFileUtil.createNewFile("fileStyleHandler03.xls"); + fileStyleTemplate07 = TestFileUtil.readFile("fill" + File.separator + "style.xlsx"); + fileStyleTemplate03 = TestFileUtil.readFile("fill" + File.separator + "style.xls"); + } + + @Test + public void t01Fill07() throws Exception { + fill(fileStyle07, fileStyleTemplate07); + XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(fileStyle07)); + XSSFSheet sheet = workbook.getSheetAt(0); + t01Fill07check(sheet.getRow(1)); + t01Fill07check(sheet.getRow(2)); + } + + private void t01Fill07check(XSSFRow row) { + XSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FF00B050", cell0.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF7030A0", cell0.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell0.getCellStyle().getFont().getBold()); + + XSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FF92D050", cell1.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF4BACC6", cell1.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell1.getCellStyle().getFont().getBold()); + + XSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("FFFFC000", cell2.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FFC0504D", cell2.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell2.getCellStyle().getFont().getBold()); + + XSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell3.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FFEEECE1", cell3.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell3.getCellStyle().getFont().getBold()); + + XSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFC00000", cell4.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF000000", cell4.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell4.getCellStyle().getFont().getBold()); + + XSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFF79646", cell5.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF8064A2", cell5.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell5.getCellStyle().getFont().getBold()); + } + + @Test + public void t02Fill03() throws Exception { + fill(fileStyle03, fileStyleTemplate03); + HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(fileStyle03)); + HSSFSheet sheet = workbook.getSheetAt(0); + t02Fill03check(workbook, sheet.getRow(1)); + t02Fill03check(workbook, sheet.getRow(2)); + } + + private void t02Fill03check(HSSFWorkbook workbook, HSSFRow row) { + HSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("0:8080:0", cell0.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:0:8080", cell0.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell0.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:CCCC:0", cell1.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:8080:8080", cell1.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell1.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("FFFF:CCCC:0", cell2.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:0:0", cell2.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell2.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell3.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("FFFF:FFFF:9999", cell3.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell3.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell4.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("3333:3333:3333", cell4.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell4.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell5.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("CCCC:9999:FFFF", cell5.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell5.getCellStyle().getFont(workbook).getBold()); + } + + private void fill(File file, File template) throws Exception { + EasyExcel.write(file, FillStyleData.class).withTemplate(template).sheet().doFill(data()); + } + + @Test + public void t11FillStyleHandler07() throws Exception { + fillStyleHandler(fileStyleHandler07, fileStyleTemplate07); + XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(fileStyleHandler07)); + XSSFSheet sheet = workbook.getSheetAt(0); + t11FillStyleHandler07check(sheet.getRow(1)); + t11FillStyleHandler07check(sheet.getRow(2)); + } + + private void t11FillStyleHandler07check(XSSFRow row) { + XSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFFFF00", cell0.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF808000", cell0.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell0.getCellStyle().getFont().getBold()); + + XSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell1.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF800000", cell1.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell1.getCellStyle().getFont().getBold()); + + XSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("FF008000", cell2.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF003300", cell2.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell2.getCellStyle().getFont().getBold()); + + XSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF0000", cell3.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FFEEECE1", cell3.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertTrue(cell3.getCellStyle().getFont().getBold()); + + XSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFC00000", cell4.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF000000", cell4.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell4.getCellStyle().getFont().getBold()); + + XSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFF79646", cell5.getCellStyle().getFillForegroundColorColor().getARGBHex()); + Assertions.assertEquals("FF8064A2", cell5.getCellStyle().getFont().getXSSFColor().getARGBHex()); + Assertions.assertFalse(cell5.getCellStyle().getFont().getBold()); + } + + @Test + public void t12FillStyleHandler03() throws Exception { + fillStyleHandler(fileStyleHandler03, fileStyleTemplate03); + HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(fileStyleHandler03)); + HSSFSheet sheet = workbook.getSheetAt(0); + t12FillStyleHandler03check(workbook, sheet.getRow(1)); + t12FillStyleHandler03check(workbook, sheet.getRow(2)); + } + + private void t12FillStyleHandler03check(HSSFWorkbook workbook, HSSFRow row) { + HSSFCell cell0 = row.getCell(0); + Assertions.assertEquals("张三", cell0.getStringCellValue()); + Assertions.assertEquals(49, cell0.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:FFFF:0", cell0.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:8080:0", cell0.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell0.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell1 = row.getCell(1); + Assertions.assertEquals(5.2, cell1.getNumericCellValue(), 1); + Assertions.assertEquals(0, cell1.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell1.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("8080:0:0", cell1.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell1.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell2 = row.getCell(2); + Assertions.assertEquals("2020-01-01 01:01:01", + DateUtils.format(cell2.getDateCellValue(), "yyyy-MM-dd HH:mm:ss")); + Assertions.assertEquals("yyyy-MM-dd HH:mm:ss", cell2.getCellStyle().getDataFormatString()); + Assertions.assertEquals("0:8080:0", cell2.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("0:3333:0", cell2.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell2.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell3 = row.getCell(3); + Assertions.assertEquals("张三今年5.2岁了", cell3.getStringCellValue()); + Assertions.assertEquals(0, cell3.getCellStyle().getDataFormat()); + Assertions.assertEquals("FFFF:0:0", cell3.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("FFFF:FFFF:9999", cell3.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertTrue(cell3.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell4 = row.getCell(4); + Assertions.assertEquals("{.name}忽略,张三", cell4.getStringCellValue()); + Assertions.assertEquals(0, cell4.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell4.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("3333:3333:3333", cell4.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell4.getCellStyle().getFont(workbook).getBold()); + + HSSFCell cell5 = row.getCell(5); + Assertions.assertEquals("空", cell5.getStringCellValue()); + Assertions.assertEquals(0, cell5.getCellStyle().getDataFormat()); + Assertions.assertEquals("9999:3333:0", cell5.getCellStyle().getFillForegroundColorColor().getHexString()); + Assertions.assertEquals("CCCC:9999:FFFF", cell5.getCellStyle().getFont(workbook).getHSSFColor(workbook) + .getHexString()); + Assertions.assertFalse(cell5.getCellStyle().getFont(workbook).getBold()); + } + + private void fillStyleHandler(File file, File template) throws Exception { + EasyExcel.write(file, FillStyleData.class).withTemplate(template).sheet() + .registerWriteHandler(new AbstractVerticalCellStyleStrategy() { + + @Override + protected WriteCellStyle contentCellStyle(CellWriteHandlerContext context) { + WriteCellStyle writeCellStyle = new WriteCellStyle(); + WriteFont writeFont = new WriteFont(); + writeCellStyle.setWriteFont(writeFont); + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + writeFont.setBold(true); + if (context.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + writeFont.setColor(IndexedColors.DARK_YELLOW.getIndex()); + } + if (context.getColumnIndex() == 1) { + writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + writeFont.setColor(IndexedColors.DARK_RED.getIndex()); + } + if (context.getColumnIndex() == 2) { + writeCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + writeFont.setColor(IndexedColors.DARK_GREEN.getIndex()); + } + if (context.getColumnIndex() == 3) { + writeCellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + writeFont.setColor(IndexedColors.DARK_BLUE.getIndex()); + } + if (context.getColumnIndex() == 4) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + writeFont.setColor(IndexedColors.DARK_YELLOW.getIndex()); + } + if (context.getColumnIndex() == 5) { + writeCellStyle.setFillForegroundColor(IndexedColors.TEAL.getIndex()); + writeFont.setColor(IndexedColors.DARK_TEAL.getIndex()); + } + return writeCellStyle; + } + + @Override + protected WriteCellStyle headCellStyle(Head head) { + return null; + } + + }) + .doFill(data()); + } + + private List data() throws Exception { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + FillStyleData fillData = new FillStyleData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + fillData.setDate(DateUtils.parseDate("2020-01-01 01:01:01")); + if (i == 5) { + fillData.setName(null); + } + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandler.java new file mode 100644 index 0000000..d91ecba --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandler.java @@ -0,0 +1,277 @@ +package ai.chat2db.excel.test.core.handler; + +import java.util.List; + +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import ai.chat2db.excel.write.handler.WorkbookWriteHandler; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.junit.jupiter.api.Assertions; + +/** + * @author JiaJu Zhuang + **/ +public class WriteHandler implements WorkbookWriteHandler, SheetWriteHandler, RowWriteHandler, CellWriteHandler { + + private long beforeCellCreate = 0L; + private long afterCellCreate = 0L; + private long afterCellDataConverted = 0L; + private long afterCellDispose = 0L; + private long beforeRowCreate = 0L; + private long afterRowCreate = 0L; + private long afterRowDispose = 0L; + private long beforeSheetCreate = 0L; + private long afterSheetCreate = 0L; + private long beforeWorkbookCreate = 0L; + private long afterWorkbookCreate = 0L; + private long afterWorkbookDispose = 0L; + + @Override + public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + beforeCellCreate++; + } + + } + + @Override + public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, + Head head, Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterCellCreate++; + } + } + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + WriteCellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(1L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(1, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(1L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterCellDataConverted++; + } + + @Override + public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + List> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(1L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterCellDispose++; + } + } + + @Override + public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, + Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(0L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + beforeRowCreate++; + } + + } + + @Override + public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterRowCreate++; + } + } + + @Override + public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(1L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(1L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterRowDispose++; + } + } + + @Override + public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(0L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(0L, beforeSheetCreate); + Assertions.assertEquals(0L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + beforeSheetCreate++; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(0L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(0L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterSheetCreate++; + } + + @Override + public void beforeWorkbookCreate() { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(0L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(0L, beforeSheetCreate); + Assertions.assertEquals(0L, afterSheetCreate); + Assertions.assertEquals(0L, beforeWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + beforeWorkbookCreate++; + } + + @Override + public void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) { + Assertions.assertEquals(0L, beforeCellCreate); + Assertions.assertEquals(0L, afterCellCreate); + Assertions.assertEquals(0L, afterCellDataConverted); + Assertions.assertEquals(0L, afterCellDispose); + Assertions.assertEquals(0L, beforeRowCreate); + Assertions.assertEquals(0L, afterRowCreate); + Assertions.assertEquals(0L, afterRowDispose); + Assertions.assertEquals(0L, beforeSheetCreate); + Assertions.assertEquals(0L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterWorkbookCreate++; + } + + @Override + public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(1L, afterCellCreate); + Assertions.assertEquals(1L, afterCellDataConverted); + Assertions.assertEquals(1L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(1L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(0L, afterWorkbookDispose); + afterWorkbookDispose++; + + } + + public void afterAll() { + Assertions.assertEquals(1L, beforeCellCreate); + Assertions.assertEquals(1L, afterCellCreate); + Assertions.assertEquals(1L, afterCellDataConverted); + Assertions.assertEquals(1L, afterCellDispose); + Assertions.assertEquals(1L, beforeRowCreate); + Assertions.assertEquals(1L, afterRowCreate); + Assertions.assertEquals(1L, afterRowDispose); + Assertions.assertEquals(1L, beforeSheetCreate); + Assertions.assertEquals(1L, afterSheetCreate); + Assertions.assertEquals(1L, beforeWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookCreate); + Assertions.assertEquals(1L, afterWorkbookDispose); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerData.java new file mode 100644 index 0000000..636e40a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.handler; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteHandlerData { + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerTest.java new file mode 100644 index 0000000..615599b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/handler/WriteHandlerTest.java @@ -0,0 +1,105 @@ +package ai.chat2db.excel.test.core.handler; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class WriteHandlerTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("writeHandler07.xlsx"); + file03 = TestFileUtil.createNewFile("writeHandler03.xls"); + fileCsv = TestFileUtil.createNewFile("writeHandlerCsv.csv"); + } + + @Test + public void t01WorkbookWrite07() throws Exception { + workbookWrite(file07); + } + + @Test + public void t02WorkbookWrite03() throws Exception { + workbookWrite(file03); + } + + @Test + public void t03WorkbookWriteCsv() throws Exception { + workbookWrite(fileCsv); + } + + @Test + public void t11SheetWrite07() throws Exception { + sheetWrite(file07); + } + + @Test + public void t12SheetWrite03() throws Exception { + sheetWrite(file03); + } + + @Test + public void t13SheetWriteCsv() throws Exception { + sheetWrite(fileCsv); + } + + @Test + public void t21TableWrite07() throws Exception { + tableWrite(file07); + } + + @Test + public void t22TableWrite03() throws Exception { + tableWrite(file03); + } + + @Test + public void t23TableWriteCsv() throws Exception { + tableWrite(fileCsv); + } + + private void workbookWrite(File file) { + WriteHandler writeHandler = new WriteHandler(); + EasyExcel.write(file).head(WriteHandlerData.class).registerWriteHandler(writeHandler).sheet().doWrite(data()); + writeHandler.afterAll(); + } + + private void sheetWrite(File file) { + WriteHandler writeHandler = new WriteHandler(); + EasyExcel.write(file).head(WriteHandlerData.class).sheet().registerWriteHandler(writeHandler).doWrite(data()); + writeHandler.afterAll(); + } + + private void tableWrite(File file) { + WriteHandler writeHandler = new WriteHandler(); + EasyExcel.write(file).head(WriteHandlerData.class).sheet().table(0).registerWriteHandler(writeHandler) + .doWrite(data()); + writeHandler.afterAll(); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 1; i++) { + WriteHandlerData data = new WriteHandlerData(); + data.setName("姓名" + i); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexDataListener.java new file mode 100644 index 0000000..93e9524 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexDataListener.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.core.head; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ComplexDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ComplexHeadData.class); + List list = new ArrayList(); + + @Override + public void invoke(ComplexHeadData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + ComplexHeadData data = list.get(0); + Assertions.assertEquals(data.getString4(), "字符串4"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadData.java new file mode 100644 index 0000000..f2a871f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.core.head; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ComplexHeadData { + @ExcelProperty({"顶格", "顶格", "两格"}) + private String string0; + @ExcelProperty({"顶格", "顶格", "两格"}) + private String string1; + @ExcelProperty({"顶格", "四联", "四联"}) + private String string2; + @ExcelProperty({"顶格", "四联", "四联"}) + private String string3; + @ExcelProperty({"顶格"}) + private String string4; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadDataTest.java new file mode 100644 index 0000000..41edb51 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ComplexHeadDataTest.java @@ -0,0 +1,90 @@ +package ai.chat2db.excel.test.core.head; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ComplexHeadDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File file07AutomaticMergeHead; + private static File file03AutomaticMergeHead; + private static File fileCsvAutomaticMergeHead; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("complexHead07.xlsx"); + file03 = TestFileUtil.createNewFile("complexHead03.xls"); + fileCsv = TestFileUtil.createNewFile("complexHeadCsv.csv"); + file07AutomaticMergeHead = TestFileUtil.createNewFile("complexHeadAutomaticMergeHead07.xlsx"); + file03AutomaticMergeHead = TestFileUtil.createNewFile("complexHeadAutomaticMergeHead03.xls"); + fileCsvAutomaticMergeHead = TestFileUtil.createNewFile("complexHeadAutomaticMergeHeadCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, ComplexHeadData.class).sheet().doWrite(data()); + EasyExcel.read(file, ComplexHeadData.class, new ComplexDataListener()) + .xlsxSAXParserFactoryName("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl").sheet().doRead(); + } + + @Test + public void t11ReadAndWriteAutomaticMergeHead07() { + readAndWriteAutomaticMergeHead(file07AutomaticMergeHead); + } + + @Test + public void t12ReadAndWriteAutomaticMergeHead03() { + readAndWriteAutomaticMergeHead(file03AutomaticMergeHead); + } + + @Test + public void t13ReadAndWriteAutomaticMergeHeadCsv() { + readAndWriteAutomaticMergeHead(fileCsvAutomaticMergeHead); + } + + private void readAndWriteAutomaticMergeHead(File file) { + EasyExcel.write(file, ComplexHeadData.class).automaticMergeHead(Boolean.FALSE).sheet().doWrite(data()); + EasyExcel.read(file, ComplexHeadData.class, new ComplexDataListener()).sheet().doRead(); + } + + private List data() { + List list = new ArrayList(); + ComplexHeadData data = new ComplexHeadData(); + data.setString0("字符串0"); + data.setString1("字符串1"); + data.setString2("字符串2"); + data.setString3("字符串3"); + data.setString4("字符串4"); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataListener.java new file mode 100644 index 0000000..ff74384 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataListener.java @@ -0,0 +1,48 @@ +package ai.chat2db.excel.test.core.head; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.data.ReadCellData; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ListHeadDataListener implements ReadListener> { + + private static final Logger LOGGER = LoggerFactory.getLogger(NoHeadData.class); + List> list = new ArrayList>(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + Assertions.assertNotNull(context.readRowHolder().getRowIndex()); + headMap.forEach((key, value) -> { + Assertions.assertEquals(value.getRowIndex(), context.readRowHolder().getRowIndex()); + Assertions.assertEquals(value.getColumnIndex(), key); + }); + } + + @Override + public void invoke(Map data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + Map data = list.get(0); + Assertions.assertEquals("字符串0", data.get(0)); + Assertions.assertEquals("1", data.get(1)); + Assertions.assertEquals("2020-01-01 01:01:01", data.get(2)); + Assertions.assertEquals("额外数据", data.get(3)); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataTest.java new file mode 100644 index 0000000..f27ba75 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/ListHeadDataTest.java @@ -0,0 +1,78 @@ +package ai.chat2db.excel.test.core.head; + +import java.io.File; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ListHeadDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("listHead07.xlsx"); + file03 = TestFileUtil.createNewFile("listHead03.xls"); + fileCsv = TestFileUtil.createNewFile("listHeadCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) throws Exception { + EasyExcel.write(file).head(head()).sheet().doWrite(data()); + EasyExcel.read(file).registerReadListener(new ListHeadDataListener()).sheet().doRead(); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = new ArrayList(); + head0.add("字符串"); + List head1 = new ArrayList(); + head1.add("数字"); + List head2 = new ArrayList(); + head2.add("日期"); + list.add(head0); + list.add(head1); + list.add(head2); + return list; + } + + private List> data() throws ParseException { + List> list = new ArrayList>(); + List data0 = new ArrayList(); + data0.add("字符串0"); + data0.add(1); + data0.add(DateUtils.parseDate("2020-01-01 01:01:01")); + data0.add("额外数据"); + list.add(data0); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadData.java new file mode 100644 index 0000000..8c4f9d9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.head; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class NoHeadData { + @ExcelProperty("字符串") + private String string; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataListener.java new file mode 100644 index 0000000..ca75ec2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataListener.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.core.head; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class NoHeadDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(NoHeadData.class); + List list = new ArrayList(); + + @Override + public void invoke(NoHeadData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + NoHeadData data = list.get(0); + Assertions.assertEquals(data.getString(), "字符串0"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataTest.java new file mode 100644 index 0000000..1b19cf7 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/head/NoHeadDataTest.java @@ -0,0 +1,59 @@ +package ai.chat2db.excel.test.core.head; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class NoHeadDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("noHead07.xlsx"); + file03 = TestFileUtil.createNewFile("noHead03.xls"); + fileCsv = TestFileUtil.createNewFile("noHeadCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, NoHeadData.class).needHead(Boolean.FALSE).sheet().doWrite(data()); + EasyExcel.read(file, NoHeadData.class, new NoHeadDataListener()).headRowNumber(0).sheet().doRead(); + } + + private List data() { + List list = new ArrayList(); + NoHeadData data = new NoHeadData(); + data.setString("字符串0"); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeData.java new file mode 100644 index 0000000..0d2236f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeData.java @@ -0,0 +1,64 @@ +package ai.chat2db.excel.test.core.large; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class LargeData { + + private String str1; + + private String str2; + + private String str3; + + private String str4; + + private String str5; + + private String str6; + + private String str7; + + private String str8; + + private String str9; + + private String str10; + + private String str11; + + private String str12; + + private String str13; + + private String str14; + + private String str15; + + private String str16; + + private String str17; + + private String str18; + + private String str19; + + private String str20; + + private String str21; + + private String str22; + + private String str23; + + private String str24; + + private String str25; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataListener.java new file mode 100644 index 0000000..ff15349 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataListener.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.core.large; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.support.ExcelTypeEnum; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class LargeDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(LargeDataListener.class); + private int count = 0; + + @Override + public void invoke(LargeData data, AnalysisContext context) { + if (count == 0) { + LOGGER.info("First row:{}", JSON.toJSONString(data)); + } + count++; + if (count % 100000 == 0) { + LOGGER.info("Already read:{},{}", count, JSON.toJSONString(data)); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("Large row count:{}", count); + if (context.readWorkbookHolder().getExcelType() != ExcelTypeEnum.CSV) { + Assertions.assertEquals(count, 464509); + } else { + Assertions.assertEquals(count, 499999); + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataTest.java new file mode 100644 index 0000000..660970e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/large/LargeDataTest.java @@ -0,0 +1,165 @@ +package ai.chat2db.excel.test.core.large; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import org.apache.poi.xssf.streaming.SXSSFCell; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class LargeDataTest { + private static final Logger LOGGER = LoggerFactory.getLogger(LargeDataTest.class); + private static File fileFill07; + private static File template07; + private static File fileCsv; + private static File fileWrite07; + private static File fileWriteTemp07; + private static File fileWritePoi07; + + private int i = 0; + + @BeforeAll + public static void init() { + fileFill07 = TestFileUtil.createNewFile("largefill07.xlsx"); + fileWrite07 = TestFileUtil.createNewFile("large" + File.separator + "fileWrite07.xlsx"); + fileWriteTemp07 = TestFileUtil.createNewFile("large" + File.separator + "fileWriteTemp07.xlsx"); + fileWritePoi07 = TestFileUtil.createNewFile("large" + File.separator + "fileWritePoi07.xlsx"); + template07 = TestFileUtil.readFile("large" + File.separator + "fill.xlsx"); + fileCsv = TestFileUtil.createNewFile("largefileCsv.csv"); + } + + @Test + public void t01Read() throws Exception { + long start = System.currentTimeMillis(); + EasyExcel.read(TestFileUtil.getPath() + "large" + File.separator + "large07.xlsx", LargeData.class, + new LargeDataListener()).headRowNumber(2).sheet().doRead(); + LOGGER.info("Large data total time spent:{}", System.currentTimeMillis() - start); + } + + @Test + public void t02Fill() { + try (ExcelWriter excelWriter = EasyExcel.write(fileFill07).withTemplate(template07).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 5000; j++) { + excelWriter.fill(data(), writeSheet); + LOGGER.info("{} fill success.", j); + } + } + } + + @Test + public void t03ReadAndWriteCsv() { + // write + long start = System.currentTimeMillis(); + try (ExcelWriter excelWriter = EasyExcel.write(fileCsv).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 5000; j++) { + excelWriter.write(data(), writeSheet); + LOGGER.info("{} write success.", j); + } + } + LOGGER.info("CSV large data total time spent:{}", System.currentTimeMillis() - start); + + // read + start = System.currentTimeMillis(); + EasyExcel.read(fileCsv, LargeData.class, new LargeDataListener()).sheet().doRead(); + LOGGER.info("CSV large data total time spent:{}", System.currentTimeMillis() - start); + } + + @Test + public void t04Write() throws Exception { + ExcelWriter excelWriter = EasyExcel.write(fileWriteTemp07, LargeData.class).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 2; j++) { + excelWriter.write(data(), writeSheet); + } + excelWriter.finish(); + + long start = System.currentTimeMillis(); + excelWriter = EasyExcel.write(fileWrite07, LargeData.class).build(); + writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 5000; j++) { + excelWriter.write(data(), writeSheet); + LOGGER.info("{} write success.", j); + } + excelWriter.finish(); + long cost = System.currentTimeMillis() - start; + LOGGER.info("write cost:{}", cost); + start = System.currentTimeMillis(); + try (FileOutputStream fileOutputStream = new FileOutputStream(fileWritePoi07)) { + SXSSFWorkbook workbook = new SXSSFWorkbook(); + SXSSFSheet sheet = workbook.createSheet("sheet1"); + for (int i = 0; i < 100 * 5000; i++) { + SXSSFRow row = sheet.createRow(i); + for (int j = 0; j < 25; j++) { + SXSSFCell cell = row.createCell(j); + cell.setCellValue("str-" + j + "-" + i); + } + if (i % 5000 == 0) { + LOGGER.info("{} write success.", i); + } + } + workbook.write(fileOutputStream); + workbook.dispose(); + workbook.close(); + } + long costPoi = System.currentTimeMillis() - start; + LOGGER.info("poi write cost:{}", System.currentTimeMillis() - start); + LOGGER.info("{} vs {}", cost, costPoi); + Assertions.assertTrue(costPoi * 2 > cost); + } + + private List data() { + List list = new ArrayList<>(); + int size = i + 100; + for (; i < size; i++) { + LargeData largeData = new LargeData(); + list.add(largeData); + largeData.setStr1("str1-" + i); + largeData.setStr2("str2-" + i); + largeData.setStr3("str3-" + i); + largeData.setStr4("str4-" + i); + largeData.setStr5("str5-" + i); + largeData.setStr6("str6-" + i); + largeData.setStr7("str7-" + i); + largeData.setStr8("str8-" + i); + largeData.setStr9("str9-" + i); + largeData.setStr10("str10-" + i); + largeData.setStr11("str11-" + i); + largeData.setStr12("str12-" + i); + largeData.setStr13("str13-" + i); + largeData.setStr14("str14-" + i); + largeData.setStr15("str15-" + i); + largeData.setStr16("str16-" + i); + largeData.setStr17("str17-" + i); + largeData.setStr18("str18-" + i); + largeData.setStr19("str19-" + i); + largeData.setStr20("str20-" + i); + largeData.setStr21("str21-" + i); + largeData.setStr22("str22-" + i); + largeData.setStr23("str23-" + i); + largeData.setStr24("str24-" + i); + largeData.setStr25("str25-" + i); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsData.java new file mode 100644 index 0000000..b5c10ae --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsData.java @@ -0,0 +1,15 @@ +package ai.chat2db.excel.test.core.multiplesheets; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class MultipleSheetsData { + private String title; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsDataTest.java new file mode 100644 index 0000000..3c8e097 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsDataTest.java @@ -0,0 +1,69 @@ +package ai.chat2db.excel.test.core.multiplesheets; + +import java.io.File; +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class MultipleSheetsDataTest { + + private static File file07; + private static File file03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.readFile("multiplesheets" + File.separator + "multiplesheets.xlsx"); + file03 = TestFileUtil.readFile("multiplesheets" + File.separator + "multiplesheets.xls"); + } + + @Test + public void t01Read07() { + read(file07); + } + + @Test + public void t02Read03() { + read(file03); + } + + @Test + public void t03Read07All() { + readAll(file07); + } + + @Test + public void t04Read03All() { + readAll(file03); + } + + private void read(File file) { + MultipleSheetsListener multipleSheetsListener = new MultipleSheetsListener(); + try (ExcelReader excelReader = EasyExcel.read(file, MultipleSheetsData.class, multipleSheetsListener).build()) { + List sheets = excelReader.excelExecutor().sheetList(); + int count = 1; + for (ReadSheet readSheet : sheets) { + excelReader.read(readSheet); + Assertions.assertEquals(multipleSheetsListener.getList().size(), count); + count++; + } + } + } + + private void readAll(File file) { + EasyExcel.read(file, MultipleSheetsData.class, new MultipleSheetsListener()).doReadAll(); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsListener.java new file mode 100644 index 0000000..40f7e10 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/multiplesheets/MultipleSheetsListener.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.test.core.multiplesheets; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class MultipleSheetsListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(MultipleSheetsListener.class); + List list = new ArrayList(); + + @Override + public void invoke(MultipleSheetsData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.debug("A form is read finished."); + Assertions.assertEquals(list.get(0).getTitle(), "表1数据"); + LOGGER.debug("All row:{}", JSON.toJSONString(list)); + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/nomodel/NoModelDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/nomodel/NoModelDataTest.java new file mode 100644 index 0000000..5100835 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/nomodel/NoModelDataTest.java @@ -0,0 +1,128 @@ +package ai.chat2db.excel.test.core.nomodel; + +import java.io.File; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.enums.ReadDefaultReturnEnum; +import ai.chat2db.excel.util.DateUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.data.ReadCellData; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class NoModelDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File fileRepeat07; + private static File fileRepeat03; + private static File fileRepeatCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("noModel07.xlsx"); + file03 = TestFileUtil.createNewFile("noModel03.xls"); + fileCsv = TestFileUtil.createNewFile("noModelCsv.csv"); + fileRepeat07 = TestFileUtil.createNewFile("noModelRepeat07.xlsx"); + fileRepeat03 = TestFileUtil.createNewFile("noModelRepeat03.xls"); + fileRepeatCsv = TestFileUtil.createNewFile("noModelRepeatCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07, fileRepeat07, false); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03, fileRepeat03, false); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv, fileRepeatCsv, true); + } + + private void readAndWrite(File file, File fileRepeat, boolean isCsv) throws Exception { + EasyExcel.write(file).sheet().doWrite(data()); + List> result = EasyExcel.read(file).headRowNumber(0).sheet().doReadSync(); + Assertions.assertEquals(10, result.size()); + Map data10 = result.get(9); + Assertions.assertEquals("string19", data10.get(0)); + Assertions.assertEquals("109", data10.get(1)); + Assertions.assertEquals("2020-01-01 01:01:01", data10.get(2)); + + List> actualDataList = EasyExcel.read(file) + .headRowNumber(0) + .readDefaultReturn(ReadDefaultReturnEnum.ACTUAL_DATA) + .sheet() + .doReadSync(); + log.info("actualDataList:{}", JSON.toJSONString(actualDataList)); + Assertions.assertEquals(10, actualDataList.size()); + Map actualData10 = actualDataList.get(9); + Assertions.assertEquals("string19", actualData10.get(0)); + if (isCsv) { + // CSV only string type + Assertions.assertEquals("109", actualData10.get(1)); + Assertions.assertEquals("2020-01-01 01:01:01", actualData10.get(2)); + } else { + Assertions.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)actualData10.get(1))); + Assertions.assertEquals(LocalDateTime.of(2020, 1, 1, 1, 1, 1), actualData10.get(2)); + } + + List>> readCellDataList = EasyExcel.read(file) + .headRowNumber(0) + .readDefaultReturn(ReadDefaultReturnEnum.READ_CELL_DATA) + .sheet() + .doReadSync(); + log.info("readCellDataList:{}", JSON.toJSONString(readCellDataList)); + Assertions.assertEquals(10, readCellDataList.size()); + Map> readCellData10 = readCellDataList.get(9); + Assertions.assertEquals("string19", readCellData10.get(0).getData()); + if (isCsv) { + // CSV only string type + Assertions.assertEquals("109", readCellData10.get(1).getData()); + Assertions.assertEquals("2020-01-01 01:01:01", readCellData10.get(2).getData()); + } else { + Assertions.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)readCellData10.get(1).getData())); + Assertions.assertEquals(LocalDateTime.of(2020, 1, 1, 1, 1, 1), readCellData10.get(2).getData()); + } + + EasyExcel.write(fileRepeat).sheet().doWrite(result); + result = EasyExcel.read(fileRepeat).headRowNumber(0).sheet().doReadSync(); + Assertions.assertEquals(10, result.size()); + data10 = result.get(9); + Assertions.assertEquals("string19", data10.get(0)); + Assertions.assertEquals("109", data10.get(1)); + Assertions.assertEquals("2020-01-01 01:01:01", data10.get(2)); + } + + private List> data() throws Exception { + List> list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + List data = new ArrayList<>(); + data.add("string1" + i); + data.add(100 + i); + data.add(DateUtils.parseDate("2020-01-01 01:01:01")); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelData.java new file mode 100644 index 0000000..77dd1d0 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelData.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.core.noncamel; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class UnCamelData { + private String string1; + private String String2; + private String sTring3; + private String STring4; + private String STRING5; + private String STRing6; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataListener.java new file mode 100644 index 0000000..0d3007e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataListener.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.test.core.noncamel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; + +/** + * @author Jiaju Zhuang + */ +@Slf4j +public class UnCamelDataListener extends AnalysisEventListener { + List list = new ArrayList<>(); + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + log.debug("Head is:{}", JSON.toJSONString(headMap)); + Assertions.assertEquals(headMap.get(0), "string1"); + Assertions.assertEquals(headMap.get(1), "string2"); + Assertions.assertEquals(headMap.get(2), "STring3"); + Assertions.assertEquals(headMap.get(3), "STring4"); + Assertions.assertEquals(headMap.get(4), "STRING5"); + Assertions.assertEquals(headMap.get(5), "STRing6"); + + } + + @Override + public void invoke(UnCamelData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 10); + UnCamelData unCamelData = list.get(0); + Assertions.assertEquals(unCamelData.getString1(), "string1"); + Assertions.assertEquals(unCamelData.getString2(), "string2"); + Assertions.assertEquals(unCamelData.getSTring3(), "string3"); + Assertions.assertEquals(unCamelData.getSTring4(), "string4"); + Assertions.assertEquals(unCamelData.getSTRING5(), "string5"); + Assertions.assertEquals(unCamelData.getSTRing6(), "string6"); + log.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataTest.java new file mode 100644 index 0000000..1f7faa4 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/noncamel/UnCamelDataTest.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.test.core.noncamel; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class UnCamelDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("unCame07.xlsx"); + file03 = TestFileUtil.createNewFile("unCame03.xls"); + fileCsv = TestFileUtil.createNewFile("unCameCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, UnCamelData.class).sheet().doWrite(data()); + EasyExcel.read(file, UnCamelData.class, new UnCamelDataListener()).sheet().doRead(); + } + + private List data() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + UnCamelData unCamelData = new UnCamelData(); + unCamelData.setString1("string1"); + unCamelData.setString2("string2"); + unCamelData.setSTring3("string3"); + unCamelData.setSTring4("string4"); + unCamelData.setSTRING5("string5"); + unCamelData.setSTRing6("string6"); + list.add(unCamelData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterData.java new file mode 100644 index 0000000..45e3af1 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.parameter; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class ParameterData { + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataListener.java new file mode 100644 index 0000000..e821d9c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataListener.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.core.parameter; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class ParameterDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ParameterDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(ParameterData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 10); + Assertions.assertEquals(list.get(0).getName(), "姓名0"); + Assertions.assertEquals((int)(context.readSheetHolder().getSheetNo()), 0); + Assertions.assertEquals( + context.readSheetHolder().getExcelReadHeadProperty().getHeadMap().get(0).getHeadNameList().get(0), "姓名"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataTest.java new file mode 100644 index 0000000..525087d --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/parameter/ParameterDataTest.java @@ -0,0 +1,146 @@ +package ai.chat2db.excel.test.core.parameter; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.cache.MapCache; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.converters.string.StringStringConverter; +import ai.chat2db.excel.support.ExcelTypeEnum; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ParameterDataTest { + + private static File file07; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("parameter07.xlsx"); + fileCsv = TestFileUtil.createNewFile("parameterCsv.csv"); + } + + @Test + public void t01ReadAndWrite() throws Exception { + readAndWrite1(file07, ExcelTypeEnum.XLSX); + readAndWrite2(file07, ExcelTypeEnum.XLSX); + readAndWrite3(file07, ExcelTypeEnum.XLSX); + readAndWrite4(file07, ExcelTypeEnum.XLSX); + readAndWrite5(file07, ExcelTypeEnum.XLSX); + readAndWrite6(file07, ExcelTypeEnum.XLSX); + readAndWrite7(file07, ExcelTypeEnum.XLSX); + } + + @Test + public void t02ReadAndWrite() throws Exception { + readAndWrite1(fileCsv, ExcelTypeEnum.CSV); + readAndWrite2(fileCsv, ExcelTypeEnum.CSV); + readAndWrite3(fileCsv, ExcelTypeEnum.CSV); + readAndWrite4(fileCsv, ExcelTypeEnum.CSV); + readAndWrite5(fileCsv, ExcelTypeEnum.CSV); + readAndWrite6(fileCsv, ExcelTypeEnum.CSV); + readAndWrite7(fileCsv, ExcelTypeEnum.CSV); + } + + private void readAndWrite1(File file, ExcelTypeEnum type) { + EasyExcel.write(file.getPath()).head(ParameterData.class).sheet().doWrite(data()); + EasyExcel.read(file.getPath()).head(ParameterData.class).registerReadListener(new ParameterDataListener()) + .sheet().doRead(); + } + + private void readAndWrite2(File file, ExcelTypeEnum type) { + EasyExcel.write(file.getPath(), ParameterData.class).sheet().doWrite(data()); + EasyExcel.read(file.getPath(), ParameterData.class, new ParameterDataListener()).sheet().doRead(); + } + + private void readAndWrite3(File file, ExcelTypeEnum type) throws Exception { + EasyExcel.write(new FileOutputStream(file)).excelType(type).head(ParameterData.class).sheet() + .doWrite(data()); + EasyExcel.read(file.getPath()).head(ParameterData.class).registerReadListener(new ParameterDataListener()) + .sheet().doRead(); + } + + private void readAndWrite4(File file, ExcelTypeEnum type) throws Exception { + EasyExcel.write(new FileOutputStream(file), ParameterData.class).excelType(type).sheet().doWrite(data()); + EasyExcel.read(file.getPath(), new ParameterDataListener()).head(ParameterData.class).sheet().doRead(); + } + + private void readAndWrite5(File file, ExcelTypeEnum type) throws Exception { + ExcelWriter excelWriter = + EasyExcel.write(new FileOutputStream(file)).excelType(type).head(ParameterData.class).relativeHeadRowIndex( + 0).build(); + WriteSheet writeSheet = EasyExcel.writerSheet(0).relativeHeadRowIndex(0).needHead(Boolean.FALSE).build(); + WriteTable writeTable = EasyExcel.writerTable(0).relativeHeadRowIndex(0).needHead(Boolean.TRUE).build(); + excelWriter.write(data(), writeSheet, writeTable); + excelWriter.finish(); + + ExcelReader excelReader = EasyExcel.read(file.getPath(), new ParameterDataListener()).head(ParameterData.class) + .mandatoryUseInputStream(Boolean.FALSE).autoCloseStream(Boolean.TRUE).readCache(new MapCache()).build(); + ReadSheet readSheet = EasyExcel.readSheet().head(ParameterData.class).use1904windowing(Boolean.FALSE) + .headRowNumber(1).sheetNo(0).sheetName("0").build(); + excelReader.read(readSheet); + excelReader.finish(); + + excelReader = EasyExcel.read(file.getPath(), new ParameterDataListener()).head(ParameterData.class) + .mandatoryUseInputStream(Boolean.FALSE).autoCloseStream(Boolean.TRUE).readCache(new MapCache()).build(); + excelReader.read(); + excelReader.finish(); + } + + private void readAndWrite6(File file, ExcelTypeEnum type) throws Exception { + ExcelWriter excelWriter = + EasyExcel.write(new FileOutputStream(file)).excelType(type).head(ParameterData.class).relativeHeadRowIndex( + 0).build(); + WriteSheet writeSheet = EasyExcel.writerSheet(0).relativeHeadRowIndex(0).needHead(Boolean.FALSE).build(); + WriteTable writeTable = EasyExcel.writerTable(0).registerConverter(new StringStringConverter()) + .relativeHeadRowIndex(0).needHead(Boolean.TRUE).build(); + excelWriter.write(data(), writeSheet, writeTable); + excelWriter.finish(); + + ExcelReader excelReader = EasyExcel.read(file.getPath(), new ParameterDataListener()).head(ParameterData.class) + .mandatoryUseInputStream(Boolean.FALSE).autoCloseStream(Boolean.TRUE).readCache(new MapCache()).build(); + ReadSheet readSheet = EasyExcel.readSheet("0").head(ParameterData.class).use1904windowing(Boolean.FALSE) + .headRowNumber(1).sheetNo(0).build(); + excelReader.read(readSheet); + excelReader.finish(); + + excelReader = EasyExcel.read(file.getPath(), new ParameterDataListener()).head(ParameterData.class) + .mandatoryUseInputStream(Boolean.FALSE).autoCloseStream(Boolean.TRUE).readCache(new MapCache()).build(); + excelReader.read(); + excelReader.finish(); + } + + private void readAndWrite7(File file, ExcelTypeEnum type) { + EasyExcel.write(file, ParameterData.class).registerConverter(new StringStringConverter()).sheet() + .registerConverter(new StringStringConverter()).needHead(Boolean.FALSE).table(0).needHead(Boolean.TRUE) + .doWrite(data()); + EasyExcel.read(file.getPath()).head(ParameterData.class).registerReadListener(new ParameterDataListener()) + .sheet().registerConverter(new StringStringConverter()).doRead(); + } + + private List data() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + ParameterData simpleData = new ParameterData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionData.java new file mode 100644 index 0000000..3db1c3b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.repetition; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class RepetitionData { + @ExcelProperty("字符串") + private String string; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataListener.java new file mode 100644 index 0000000..e79a40e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataListener.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.test.core.repetition; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.core.simple.SimpleDataListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class RepetitionDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(RepetitionData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 2); + Assertions.assertEquals(list.get(0).getString(), "字符串0"); + Assertions.assertEquals(list.get(1).getString(), "字符串0"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataTest.java new file mode 100644 index 0000000..0710d38 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/repetition/RepetitionDataTest.java @@ -0,0 +1,105 @@ +package ai.chat2db.excel.test.core.repetition; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class RepetitionDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File fileTable07; + private static File fileTable03; + private static File fileTableCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("repetition07.xlsx"); + file03 = TestFileUtil.createNewFile("repetition03.xls"); + fileCsv = TestFileUtil.createNewFile("repetitionCsv.csv"); + fileTable07 = TestFileUtil.createNewFile("repetitionTable07.xlsx"); + fileTable03 = TestFileUtil.createNewFile("repetitionTable03.xls"); + fileTableCsv = TestFileUtil.createNewFile("repetitionTableCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + try (ExcelWriter excelWriter = EasyExcel.write(file, RepetitionData.class).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet(0).build(); + excelWriter.write(data(), writeSheet).write(data(), writeSheet); + } + try (ExcelReader excelReader = EasyExcel.read(file, RepetitionData.class, new RepetitionDataListener()) + .build()) { + ReadSheet readSheet = EasyExcel.readSheet(0).build(); + excelReader.read(readSheet); + } + } + + @Test + public void t11ReadAndWriteTable07() { + readAndWriteTable(fileTable07); + } + + @Test + public void t12ReadAndWriteTable03() { + readAndWriteTable(fileTable03); + } + + @Test + public void t13ReadAndWriteTableCsv() { + readAndWriteTable(fileTableCsv); + } + + private void readAndWriteTable(File file) { + try (ExcelWriter excelWriter = EasyExcel.write(file, RepetitionData.class).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet(0).build(); + WriteTable writeTable = EasyExcel.writerTable(0).relativeHeadRowIndex(0).build(); + excelWriter.write(data(), writeSheet, writeTable).write(data(), writeSheet, writeTable); + } + try (ExcelReader excelReader = EasyExcel.read(file, RepetitionData.class, new RepetitionDataListener()) + .build()) { + ReadSheet readSheet = EasyExcel.readSheet(0).headRowNumber(2).build(); + excelReader.read(readSheet); + } + } + + private List data() { + List list = new ArrayList(); + RepetitionData data = new RepetitionData(); + data.setString("字符串0"); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleData.java new file mode 100644 index 0000000..bf2d3a4 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.core.simple; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class SimpleData { + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataListener.java new file mode 100644 index 0000000..e3bc09c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataListener.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.test.core.simple; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class SimpleDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataListener.class); + List list = new ArrayList(); + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.debug("Head is:{}", JSON.toJSONString(headMap)); + Assertions.assertEquals(headMap.get(0), "姓名"); + } + + @Override + public void invoke(SimpleData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 10); + Assertions.assertEquals(list.get(0).getName(), "姓名0"); + Assertions.assertEquals((int)(context.readSheetHolder().getSheetNo()), 0); + Assertions.assertEquals( + context.readSheetHolder().getExcelReadHeadProperty().getHeadMap().get(0).getHeadNameList().get(0), "姓名"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataSheetNameListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataSheetNameListener.java new file mode 100644 index 0000000..b70c0c8 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataSheetNameListener.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.test.core.simple; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class SimpleDataSheetNameListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataSheetNameListener.class); + List list = new ArrayList(); + + @Override + public void invoke(SimpleData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + Assertions.assertEquals(list.get(0).getName(), "张三"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataTest.java new file mode 100644 index 0000000..85ce745 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/simple/SimpleDataTest.java @@ -0,0 +1,130 @@ +package ai.chat2db.excel.test.core.simple; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.PageReadListener; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.support.ExcelTypeEnum; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +@Slf4j +public class SimpleDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("simple07.xlsx"); + file03 = TestFileUtil.createNewFile("simple03.xls"); + fileCsv = TestFileUtil.createNewFile("simpleCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, SimpleData.class).sheet().doWrite(data()); + EasyExcel.read(file, SimpleData.class, new SimpleDataListener()).sheet().doRead(); + } + + @Test + public void t04ReadAndWrite07() throws Exception { + readAndWriteInputStream(file07, ExcelTypeEnum.XLSX); + } + + @Test + public void t05ReadAndWrite03() throws Exception { + readAndWriteInputStream(file03, ExcelTypeEnum.XLS); + } + + @Test + public void t06ReadAndWriteCsv() throws Exception { + readAndWriteInputStream(fileCsv, ExcelTypeEnum.CSV); + } + + private void readAndWriteInputStream(File file, ExcelTypeEnum excelTypeEnum) throws Exception { + EasyExcel.write(new FileOutputStream(file), SimpleData.class).excelType(excelTypeEnum).sheet().doWrite(data()); + EasyExcel.read(new FileInputStream(file), SimpleData.class, new SimpleDataListener()).sheet().doRead(); + } + + @Test + public void t11SynchronousRead07() { + synchronousRead(file07); + } + + @Test + public void t12SynchronousRead03() { + synchronousRead(file03); + } + + @Test + public void t13SynchronousReadCsv() { + synchronousRead(fileCsv); + } + + @Test + public void t21SheetNameRead07() { + List> list = EasyExcel.read( + TestFileUtil.readFile("simple" + File.separator + "simple07.xlsx")) + .sheet("simple") + .doReadSync(); + Assertions.assertEquals(1, list.size()); + } + + @Test + public void t22PageReadListener07() { + EasyExcel.read(file07, SimpleData.class, + new PageReadListener(dataList -> { + Assertions.assertEquals(5, dataList.size()); + }, 5)) + .sheet().doRead(); + } + + private void synchronousRead(File file) { + // Synchronous read file + List list = EasyExcel.read(file).head(SimpleData.class).sheet().doReadSync(); + Assertions.assertEquals(list.size(), 10); + Assertions.assertTrue(list.get(0) instanceof SimpleData); + Assertions.assertEquals(((SimpleData)list.get(0)).getName(), "姓名0"); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + SimpleData simpleData = new SimpleData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipData.java new file mode 100644 index 0000000..895c07f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipData.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.test.core.skip; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class SkipData { + + @ExcelProperty("姓名") + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipDataTest.java new file mode 100644 index 0000000..a15fe4b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/skip/SkipDataTest.java @@ -0,0 +1,91 @@ +package ai.chat2db.excel.test.core.skip; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.SyncReadListener; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.test.core.simple.SimpleData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.exception.ExcelGenerateException; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class SkipDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("skip.xlsx"); + file03 = TestFileUtil.createNewFile("skip.xls"); + fileCsv = TestFileUtil.createNewFile("skip.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + Assertions.assertThrows(ExcelGenerateException.class, () -> readAndWrite(fileCsv)); + } + + private void readAndWrite(File file) { + try (ExcelWriter excelWriter = EasyExcel.write(file, SimpleData.class).build();) { + WriteSheet writeSheet0 = EasyExcel.writerSheet(0, "第一个").build(); + WriteSheet writeSheet1 = EasyExcel.writerSheet(1, "第二个").build(); + WriteSheet writeSheet2 = EasyExcel.writerSheet(2, "第三个").build(); + WriteSheet writeSheet3 = EasyExcel.writerSheet(3, "第四个").build(); + excelWriter.write(data("name1"), writeSheet0); + excelWriter.write(data("name2"), writeSheet1); + excelWriter.write(data("name3"), writeSheet2); + excelWriter.write(data("name4"), writeSheet3); + } + + List list = EasyExcel.read(file, SkipData.class, null).sheet("第二个").doReadSync(); + Assertions.assertEquals(1, list.size()); + Assertions.assertEquals("name2", list.get(0).getName()); + + SyncReadListener syncReadListener = new SyncReadListener(); + try (ExcelReader excelReader = EasyExcel.read(file, SkipData.class, null).registerReadListener(syncReadListener) + .build()) { + ReadSheet readSheet1 = EasyExcel.readSheet("第二个").build(); + ReadSheet readSheet3 = EasyExcel.readSheet("第四个").build(); + excelReader.read(readSheet1, readSheet3); + List syncList = syncReadListener.getList(); + Assertions.assertEquals(2, syncList.size()); + Assertions.assertEquals("name2", ((SkipData)syncList.get(0)).getName()); + Assertions.assertEquals("name4", ((SkipData)syncList.get(1)).getName()); + } + } + + private List data(String name) { + List list = new ArrayList(); + SkipData data = new SkipData(); + data.setName(name); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortData.java new file mode 100644 index 0000000..0a300e2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.core.sort; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class SortData { + private String column5; + private String column6; + @ExcelProperty(order = 100) + private String column4; + @ExcelProperty(order = 99) + private String column3; + @ExcelProperty(value = "column2", index = 1) + private String column2; + @ExcelProperty(value = "column1", index = 0) + private String column1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataListener.java new file mode 100644 index 0000000..c99775f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataListener.java @@ -0,0 +1,37 @@ +package ai.chat2db.excel.test.core.sort; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class SortDataListener extends AnalysisEventListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(SortDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(SortData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 1); + SortData sortData = list.get(0); + Assertions.assertEquals("column1", sortData.getColumn1()); + Assertions.assertEquals("column2", sortData.getColumn2()); + Assertions.assertEquals("column3", sortData.getColumn3()); + Assertions.assertEquals("column4", sortData.getColumn4()); + Assertions.assertEquals("column5", sortData.getColumn5()); + Assertions.assertEquals("column6", sortData.getColumn6()); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataTest.java new file mode 100644 index 0000000..20bbde9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/sort/SortDataTest.java @@ -0,0 +1,123 @@ +package ai.chat2db.excel.test.core.sort; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class SortDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + private static File sortNoHead07; + private static File sortNoHead03; + private static File sortNoHeadCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("sort.xlsx"); + file03 = TestFileUtil.createNewFile("sort.xls"); + fileCsv = TestFileUtil.createNewFile("sort.csv"); + sortNoHead07 = TestFileUtil.createNewFile("sortNoHead.xlsx"); + sortNoHead03 = TestFileUtil.createNewFile("sortNoHead.xls"); + sortNoHeadCsv = TestFileUtil.createNewFile("sortNoHead.csv"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() { + readAndWrite(fileCsv); + } + + @Test + public void t11ReadAndWriteNoHead07() { + readAndWriteNoHead(sortNoHead07); + } + + @Test + public void t12ReadAndWriteNoHead03() { + readAndWriteNoHead(sortNoHead03); + } + + @Test + public void t13ReadAndWriteNoHeadCsv() { + readAndWriteNoHead(sortNoHeadCsv); + } + + private void readAndWrite(File file) { + EasyExcel.write(file, SortData.class).sheet().doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals("column1", record.get(0)); + Assertions.assertEquals("column2", record.get(1)); + Assertions.assertEquals("column3", record.get(2)); + Assertions.assertEquals("column4", record.get(3)); + Assertions.assertEquals("column5", record.get(4)); + Assertions.assertEquals("column6", record.get(5)); + + EasyExcel.read(file, SortData.class, new SortDataListener()).sheet().doRead(); + } + + private void readAndWriteNoHead(File file) { + EasyExcel.write(file).head(head()).sheet().doWrite(data()); + List> dataMap = EasyExcel.read(file).sheet().doReadSync(); + Assertions.assertEquals(1, dataMap.size()); + Map record = dataMap.get(0); + Assertions.assertEquals("column1", record.get(0)); + Assertions.assertEquals("column2", record.get(1)); + Assertions.assertEquals("column3", record.get(2)); + Assertions.assertEquals("column4", record.get(3)); + Assertions.assertEquals("column5", record.get(4)); + Assertions.assertEquals("column6", record.get(5)); + EasyExcel.read(file, SortData.class, new SortDataListener()).sheet().doRead(); + } + + private List> head() { + List> head = new ArrayList>(); + head.add(Collections.singletonList("column1")); + head.add(Collections.singletonList("column2")); + head.add(Collections.singletonList("column3")); + head.add(Collections.singletonList("column4")); + head.add(Collections.singletonList("column5")); + head.add(Collections.singletonList("column6")); + return head; + } + + private List data() { + List list = new ArrayList(); + SortData sortData = new SortData(); + sortData.setColumn1("column1"); + sortData.setColumn2("column2"); + sortData.setColumn3("column3"); + sortData.setColumn4("column4"); + sortData.setColumn5("column5"); + sortData.setColumn6("column6"); + list.add(sortData); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleData.java new file mode 100644 index 0000000..eb100b7 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleData.java @@ -0,0 +1,24 @@ +package ai.chat2db.excel.test.core.style; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@HeadStyle +@HeadFontStyle +public class StyleData { + @ExcelProperty("字符串") + private String string; + @ExcelProperty("字符串1") + private String string1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataListener.java new file mode 100644 index 0000000..9679d84 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataListener.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.test.core.style; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.core.simple.SimpleDataListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class StyleDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(StyleData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 2); + Assertions.assertEquals(list.get(0).getString(), "字符串0"); + Assertions.assertEquals(list.get(1).getString(), "字符串1"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataTest.java new file mode 100644 index 0000000..9c22822 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/style/StyleDataTest.java @@ -0,0 +1,261 @@ +package ai.chat2db.excel.test.core.style; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.write.merge.LoopMergeStrategy; +import ai.chat2db.excel.write.merge.OnceAbsoluteMergeStrategy; +import ai.chat2db.excel.write.style.AbstractVerticalCellStyleStrategy; +import ai.chat2db.excel.write.style.HorizontalCellStyleStrategy; +import ai.chat2db.excel.write.style.column.SimpleColumnWidthStyleStrategy; +import ai.chat2db.excel.write.style.row.SimpleRowHeightStyleStrategy; +import ai.chat2db.excel.test.core.StyleTestUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.DataFormatData; +import ai.chat2db.excel.metadata.property.FontProperty; +import ai.chat2db.excel.metadata.property.StyleProperty; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class StyleDataTest { + + private static File file07; + private static File file03; + private static File fileVerticalCellStyleStrategy07; + private static File fileVerticalCellStyleStrategy207; + private static File fileLoopMergeStrategy; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("style07.xlsx"); + file03 = TestFileUtil.createNewFile("style03.xls"); + fileVerticalCellStyleStrategy07 = TestFileUtil.createNewFile("verticalCellStyle.xlsx"); + fileVerticalCellStyleStrategy207 = TestFileUtil.createNewFile("verticalCellStyle2.xlsx"); + fileLoopMergeStrategy = TestFileUtil.createNewFile("loopMergeStrategy.xlsx"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03AbstractVerticalCellStyleStrategy() { + AbstractVerticalCellStyleStrategy verticalCellStyleStrategy = new AbstractVerticalCellStyleStrategy() { + @Override + protected WriteCellStyle headCellStyle(Head head) { + WriteCellStyle writeCellStyle = new WriteCellStyle(); + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + DataFormatData dataFormatData = new DataFormatData(); + dataFormatData.setIndex((short)0); + writeCellStyle.setDataFormatData(dataFormatData); + writeCellStyle.setHidden(false); + writeCellStyle.setLocked(true); + writeCellStyle.setQuotePrefix(true); + writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + writeCellStyle.setWrapped(true); + writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + writeCellStyle.setRotation((short)0); + writeCellStyle.setIndent((short)10); + writeCellStyle.setBorderLeft(BorderStyle.THIN); + writeCellStyle.setBorderRight(BorderStyle.THIN); + writeCellStyle.setBorderTop(BorderStyle.THIN); + writeCellStyle.setBorderBottom(BorderStyle.THIN); + writeCellStyle.setLeftBorderColor(IndexedColors.RED.getIndex()); + writeCellStyle.setRightBorderColor(IndexedColors.RED.getIndex()); + writeCellStyle.setTopBorderColor(IndexedColors.RED.getIndex()); + writeCellStyle.setBottomBorderColor(IndexedColors.RED.getIndex()); + writeCellStyle.setFillBackgroundColor(IndexedColors.RED.getIndex()); + writeCellStyle.setShrinkToFit(Boolean.TRUE); + + if (head.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + WriteFont writeFont = new WriteFont(); + writeFont.setItalic(true); + writeFont.setStrikeout(true); + writeFont.setTypeOffset(Font.SS_NONE); + writeFont.setUnderline(Font.U_DOUBLE); + writeFont.setBold(true); + writeFont.setCharset((int)Font.DEFAULT_CHARSET); + } else { + writeCellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + } + return writeCellStyle; + } + + @Override + protected WriteCellStyle contentCellStyle(Head head) { + WriteCellStyle writeCellStyle = new WriteCellStyle(); + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + if (head.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.DARK_GREEN.getIndex()); + } else { + writeCellStyle.setFillForegroundColor(IndexedColors.PINK.getIndex()); + } + return writeCellStyle; + } + }; + EasyExcel.write(fileVerticalCellStyleStrategy07, StyleData.class).registerWriteHandler( + verticalCellStyleStrategy).sheet() + .doWrite(data()); + + } + + @Test + public void t04AbstractVerticalCellStyleStrategy02() { + final StyleProperty styleProperty = StyleProperty.build(StyleData.class.getAnnotation(HeadStyle.class)); + final FontProperty fontProperty = FontProperty.build(StyleData.class.getAnnotation(HeadFontStyle.class)); + AbstractVerticalCellStyleStrategy verticalCellStyleStrategy = new AbstractVerticalCellStyleStrategy() { + @Override + protected WriteCellStyle headCellStyle(Head head) { + WriteCellStyle writeCellStyle = WriteCellStyle.build(styleProperty, fontProperty); + + if (head.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + WriteFont writeFont = new WriteFont(); + writeFont.setItalic(true); + writeFont.setStrikeout(true); + writeFont.setTypeOffset(Font.SS_NONE); + writeFont.setUnderline(Font.U_DOUBLE); + writeFont.setBold(true); + writeFont.setCharset((int)Font.DEFAULT_CHARSET); + } else { + writeCellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + } + return writeCellStyle; + } + + @Override + protected WriteCellStyle contentCellStyle(Head head) { + WriteCellStyle writeCellStyle = new WriteCellStyle(); + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + if (head.getColumnIndex() == 0) { + writeCellStyle.setFillForegroundColor(IndexedColors.DARK_GREEN.getIndex()); + } else { + writeCellStyle.setFillForegroundColor(IndexedColors.PINK.getIndex()); + } + return writeCellStyle; + } + }; + EasyExcel.write(fileVerticalCellStyleStrategy207, StyleData.class).registerWriteHandler( + verticalCellStyleStrategy).sheet() + .doWrite(data()); + } + + @Test + public void t05LoopMergeStrategy() { + EasyExcel.write(fileLoopMergeStrategy, StyleData.class).sheet().registerWriteHandler( + new LoopMergeStrategy(2, 1)) + .doWrite(data10()); + } + + private void readAndWrite(File file) throws Exception { + SimpleColumnWidthStyleStrategy simpleColumnWidthStyleStrategy = new SimpleColumnWidthStyleStrategy(50); + SimpleRowHeightStyleStrategy simpleRowHeightStyleStrategy = + new SimpleRowHeightStyleStrategy((short)40, (short)50); + + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + headWriteCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteFont.setColor(IndexedColors.DARK_YELLOW.getIndex()); + headWriteCellStyle.setWriteFont(headWriteFont); + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + contentWriteCellStyle.setFillForegroundColor(IndexedColors.TEAL.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + contentWriteFont.setFontHeightInPoints((short)30); + contentWriteFont.setColor(IndexedColors.DARK_TEAL.getIndex()); + contentWriteCellStyle.setWriteFont(contentWriteFont); + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(2, 2, 0, 1); + EasyExcel.write(file, StyleData.class).registerWriteHandler(simpleColumnWidthStyleStrategy) + .registerWriteHandler(simpleRowHeightStyleStrategy).registerWriteHandler(horizontalCellStyleStrategy) + .registerWriteHandler(onceAbsoluteMergeStrategy).sheet().doWrite(data()); + EasyExcel.read(file, StyleData.class, new StyleDataListener()).sheet().doRead(); + + Workbook workbook = WorkbookFactory.create(file); + Sheet sheet = workbook.getSheetAt(0); + Assertions.assertEquals(50 * 256, sheet.getColumnWidth(0), 0); + + Row row0 = sheet.getRow(0); + Assertions.assertEquals(800, row0.getHeight(), 0); + Cell cell00 = row0.getCell(0); + Assertions.assertArrayEquals(new byte[] {-1, -1, 0}, StyleTestUtils.getFillForegroundColor(cell00)); + Assertions.assertArrayEquals(new byte[] {-128, -128, 0}, StyleTestUtils.getFontColor(cell00, workbook)); + Assertions.assertEquals(20, StyleTestUtils.getFontHeightInPoints(cell00, workbook)); + + Cell cell01 = row0.getCell(1); + Assertions.assertArrayEquals(new byte[] {-1, -1, 0}, StyleTestUtils.getFillForegroundColor(cell01)); + Assertions.assertArrayEquals(new byte[] {-128, -128, 0}, StyleTestUtils.getFontColor(cell01, workbook)); + Assertions.assertEquals(20, StyleTestUtils.getFontHeightInPoints(cell01, workbook)); + + Row row1 = sheet.getRow(1); + Assertions.assertEquals(1000, row1.getHeight(), 0); + Cell cell10 = row1.getCell(0); + Assertions.assertArrayEquals(new byte[] {0, -128, -128}, StyleTestUtils.getFillForegroundColor(cell10)); + Assertions.assertArrayEquals(new byte[] {0, 51, 102}, StyleTestUtils.getFontColor(cell10, workbook)); + Assertions.assertEquals(30, StyleTestUtils.getFontHeightInPoints(cell10, workbook)); + Cell cell11 = row1.getCell(1); + Assertions.assertArrayEquals(new byte[] {0, -128, -128}, StyleTestUtils.getFillForegroundColor(cell11)); + Assertions.assertArrayEquals(new byte[] {0, 51, 102}, StyleTestUtils.getFontColor(cell11, workbook)); + Assertions.assertEquals(30, StyleTestUtils.getFontHeightInPoints(cell11, workbook)); + } + + private List data() { + List list = new ArrayList(); + StyleData data = new StyleData(); + data.setString("字符串0"); + data.setString1("字符串01"); + StyleData data1 = new StyleData(); + data1.setString("字符串1"); + data1.setString1("字符串11"); + list.add(data); + list.add(data1); + return list; + } + + private List data10() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + StyleData data = new StyleData(); + data.setString("字符串0"); + data.setString1("字符串01"); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateData.java new file mode 100644 index 0000000..f9fb164 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateData.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.core.template; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class TemplateData { + @ExcelProperty("字符串0") + private String string0; + @ExcelProperty("字符串1") + private String string1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataListener.java new file mode 100644 index 0000000..db35c44 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataListener.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.test.core.template; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.core.simple.SimpleDataListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class TemplateDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataListener.class); + List list = new ArrayList(); + + @Override + public void invoke(TemplateData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assertions.assertEquals(list.size(), 2); + Assertions.assertEquals(list.get(0).getString0(), "字符串0"); + Assertions.assertEquals(list.get(1).getString0(), "字符串1"); + LOGGER.debug("First row:{}", JSON.toJSONString(list.get(0))); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataTest.java new file mode 100644 index 0000000..7c72e60 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/core/template/TemplateDataTest.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.test.core.template; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author Jiaju Zhuang + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TemplateDataTest { + + private static File file07; + private static File file03; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("template07.xlsx"); + file03 = TestFileUtil.createNewFile("template03.xls"); + } + + @Test + public void t01ReadAndWrite07() { + readAndWrite07(file07); + } + + @Test + public void t02ReadAndWrite03() { + readAndWrite03(file03); + } + + private void readAndWrite07(File file) { + EasyExcel.write(file, TemplateData.class) + .withTemplate(TestFileUtil.readFile("template" + File.separator + "template07.xlsx")).sheet() + .doWrite(data()); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); + } + + private void readAndWrite03(File file) { + EasyExcel.write(file, TemplateData.class) + .withTemplate(TestFileUtil.readFile("template" + File.separator + "template03.xls")).sheet() + .doWrite(data()); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); + } + + private List data() { + List list = new ArrayList(); + TemplateData data = new TemplateData(); + data.setString0("字符串0"); + data.setString1("字符串01"); + TemplateData data1 = new TemplateData(); + data1.setString0("字符串1"); + data1.setString1("字符串11"); + list.add(data); + list.add(data1); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillData.java new file mode 100644 index 0000000..d7221b1 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillData.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.demo.fill; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FillData { + private String name; + private double number; + private Date date; + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillTest.java new file mode 100644 index 0000000..37c487e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/fill/FillTest.java @@ -0,0 +1,231 @@ +package ai.chat2db.excel.test.demo.fill; + +import java.io.File; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.enums.WriteDirectionEnum; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.MapUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.fill.FillConfig; +import ai.chat2db.excel.write.metadata.fill.FillWrapper; + +import org.junit.jupiter.api.Test; + +/** + * 写的填充写法 + * + * @author Jiaju Zhuang + * @since 2.1.1 + */ + +public class FillTest { + /** + * 最简单的填充 + * + * @since 2.1.1 + */ + @Test + public void simpleFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx"; + + // 方案1 根据对象填充 + String fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"; + // 这里 会填充到第一个sheet, 然后文件流会自动关闭 + FillData fillData = new FillData(); + fillData.setName("张三"); + fillData.setNumber(5.2); + EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData); + + // 方案2 根据Map填充 + fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"; + // 这里 会填充到第一个sheet, 然后文件流会自动关闭 + Map map = MapUtils.newHashMap(); + map.put("name", "张三"); + map.put("number", 5.2); + EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map); + } + + /** + * 填充列表 + * + * @since 2.1.1 + */ + @Test + public void listFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // 填充list 的时候还要注意 模板中{.} 多了个点 表示list + // 如果填充list的对象是map,必须包涵所有list的key,哪怕数据为null,必须使用map.put(key,null) + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx"; + + // 方案1 一下子全部放到内存里面 并填充 + String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; + // 这里 会填充到第一个sheet, 然后文件流会自动关闭 + EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data()); + + // 方案2 分多次 填充 会使用文件缓存(省内存) + fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; + try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + } + } + + /** + * 复杂的填充 + * + * @since 2.1.1 + */ + @Test + public void complexFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; + // 方案1 + try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 + // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 + // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存 + // 如果数据量大 list不是最后一行 参照下一个 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = MapUtils.newHashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet); + } + } + + /** + * 数据量大的复杂填充 + *

+ * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 + */ + @Test + public void complexFillWithTable() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + // 这里模板 删除了list以后的数据,也就是统计的这一行 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complexFillWithTable.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; + + // 方案1 + try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 直接写入数据 + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + + // 写入list之前的数据 + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // list 后面还有个统计 想办法手动写入 + // 这里偷懒直接用list 也可以用对象 + List> totalListList = ListUtils.newArrayList(); + List totalList = ListUtils.newArrayList(); + totalListList.add(totalList); + totalList.add(null); + totalList.add(null); + totalList.add(null); + // 第四列 + totalList.add("统计:1000"); + // 这里是write 别和fill 搞错了 + excelWriter.write(totalListList, writeSheet); + // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 + // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 + } + } + + /** + * 横向的填充 + * + * @since 2.1.1 + */ + @Test + public void horizontalFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx"; + + String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx"; + // 方案1 + try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + + Map map = new HashMap<>(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + } + } + + /** + * 多列表组合填充填充 + * + * @since 2.2.0-beta1 + */ + @Test + public void compositeFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "composite.xlsx"; + + String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx"; + + // 方案1 + try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹 + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + + Map map = new HashMap(); + //map.put("date", "2019年10月9日13:28:28"); + map.put("date", new Date()); + + excelWriter.fill(map, writeSheet); + } + } + + private List data() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + fillData.setDate(new Date()); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/rare/WriteTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/rare/WriteTest.java new file mode 100644 index 0000000..a7ebb89 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/rare/WriteTest.java @@ -0,0 +1,140 @@ +package ai.chat2db.excel.test.demo.rare; + +import java.io.File; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.WorkbookWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; +import ai.chat2db.excel.write.handler.context.WorkbookWriteHandlerContext; +import ai.chat2db.excel.test.demo.write.DemoData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.junit.jupiter.api.Test; + +/** + * 记录一些不太常见的案例 + * + * @author Jiaju Zhuang + */ + +@Slf4j +public class WriteTest { + + /** + * 压缩临时文件 + * 在导出Excel且格式为xlsx的时候会生成一个临时的xml文件,会比较大,再磁盘不太够的情况下,可以压缩。 + * 当然压缩式耗费性能的 + */ + @Test + public void compressedTemporaryFile() { + log.info("临时的xml存储在:{}", FileUtils.getPoiFilesPath()); + File file = TestFileUtil.createNewFile("rare/compressedTemporaryFile" + System.currentTimeMillis() + + ".xlsx"); + + // 这里 需要指定写用哪个class去写 + try (ExcelWriter excelWriter = EasyExcel.write(file, DemoData.class).registerWriteHandler( + new WorkbookWriteHandler() { + + /** + * 拦截Workbook创建完成事件 + * @param context + */ + @Override + public void afterWorkbookCreate(WorkbookWriteHandlerContext context) { + // 获取到Workbook对象 + Workbook workbook = context.getWriteWorkbookHolder().getWorkbook(); + // 只有SXSSFWorkbook模式才会生成临时文件 + if (workbook instanceof SXSSFWorkbook) { + SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook)workbook; + // 设置临时文件压缩,当然这个会浪费cpu性能 但是临时文件会变小 + sxssfWorkbook.setCompressTempFiles(true); + } + } + }).build()) { + // 这里注意 如果同一个sheet只要创建一次 + WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); + // 10万数据 确保有足够的空间 + for (int i = 0; i < 10000; i++) { + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + log.info("写入完毕,开始准备迁移压缩文件。"); + } + } + + /** + * 在指定单元格写入数据 + */ + @Test + public void specifiedCellWrite() { + File file = TestFileUtil.createNewFile("rare/specifiedCellWrite" + System.currentTimeMillis() + + ".xlsx"); + + // 需要区分是在 最后一行之前 还是之后 + // 区分的原因是:excel只能一直向前,而且内存里面只存储100条,而afterRowDispose是在每一行写入完成的时候调用,所以修改一行需要拦截这个事件 + // 如果是在最后一行之后,由于后面不会再有数据了,所以只要拦截afterWorkbookDispose,在整个excel快写完的时候调用,继续写入数据即可 + + EasyExcel.write(file, DemoData.class) + // 写入的值在最后一行之前 + .registerWriteHandler(new RowWriteHandler() { + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + if (context.getRow().getRowNum() == 2) { + Cell cell = context.getRow().getCell(2); + if (cell == null) { + cell = context.getRow().createCell(2); + } + cell.setCellValue("测试的第二行数据呀"); + } + } + }) + // 写入的值 在最后一一行之后 + .registerWriteHandler(new WorkbookWriteHandler() { + @Override + public void afterWorkbookDispose(WorkbookWriteHandlerContext context) { + Workbook workbook = context.getWriteWorkbookHolder().getWorkbook(); + Sheet sheet = workbook.getSheetAt(0); + Row row = sheet.getRow(99); + if (row == null) { + row = sheet.createRow(99); + } + Cell cell = row.getCell(2); + if (cell == null) { + cell = row.createCell(2); + } + cell.setCellValue("测试地99行数据呀"); + } + }) + .sheet("模板") + .doWrite(data()); + + log.info("写入到文件完成:{}", file); + } + + private List data() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + i); + data.setDate(new Date()); + data.setDoubleData(0.56); + list.add(data); + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataDemoHeadDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataDemoHeadDataListener.java new file mode 100644 index 0000000..8868e26 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataDemoHeadDataListener.java @@ -0,0 +1,48 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 读取头 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class CellDataDemoHeadDataListener implements ReadListener { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 100; + + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + @Override + public void invoke(CellDataReadDemoData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataReadDemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataReadDemoData.java new file mode 100644 index 0000000..87feee5 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CellDataReadDemoData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.Date; + +import ai.chat2db.excel.metadata.data.CellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class CellDataReadDemoData { + private CellData string; + // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number + private CellData date; + private CellData doubleData; + // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复 + private CellData formulaValue; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterData.java new file mode 100644 index 0000000..16e9cde --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterData.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.demo.read; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ConverterData { + /** + * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:” + */ + @ExcelProperty(converter = CustomStringStringConverter.class) + private String string; + /** + * 这里用string 去接日期才能格式化。我想接收年月日格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + private String date; + /** + * 我想接收百分比的数字 + */ + @NumberFormat("#.##%") + private String doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterDataListener.java new file mode 100644 index 0000000..ca0813a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ConverterDataListener.java @@ -0,0 +1,49 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class ConverterDataListener implements ReadListener { + + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + @Override + public void invoke(ConverterData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + cachedDataList.add(data); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CustomStringStringConverter.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CustomStringStringConverter.java new file mode 100644 index 0000000..9461001 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/CustomStringStringConverter.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.test.demo.read; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ReadConverterContext; +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.data.WriteCellData; + +/** + * String and string converter + * + * @author Jiaju Zhuang + */ +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 这里读的时候会调用 + * + * @param context + * @return + */ + @Override + public String convertToJavaData(ReadConverterContext context) { + return "自定义:" + context.getReadCellData().getStringValue(); + } + + /** + * 这里是写的时候会调用 不用管 + * + * @return + */ + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return new WriteCellData<>(context.getValue()); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDAO.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDAO.java new file mode 100644 index 0000000..1ed4b94 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDAO.java @@ -0,0 +1,15 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +public class DemoDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoData.java new file mode 100644 index 0000000..284777c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DemoData { + private String string; + private Date date; + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDataListener.java new file mode 100644 index 0000000..cba0e54 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoDataListener.java @@ -0,0 +1,86 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 +@Slf4j +public class DemoDataListener implements ReadListener { + + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 100; + /** + * 缓存的数据 + */ + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private DemoDAO demoDAO; + + public DemoDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + demoDAO = new DemoDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param demoDAO + */ + public DemoDataListener(DemoDAO demoDAO) { + this.demoDAO = demoDAO; + } + + /** + * 这个每一条数据解析都会来调用 + * + * @param data one row value. It is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ + @Override + public void invoke(DemoData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + cachedDataList.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + // 存储完成清理 list + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + demoDAO.save(cachedDataList); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExceptionListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExceptionListener.java new file mode 100644 index 0000000..b53a955 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExceptionListener.java @@ -0,0 +1,81 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelDataConvertException; +import ai.chat2db.excel.metadata.data.ReadCellData; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 读取转换异常 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DemoExceptionListener implements ReadListener { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + /** + * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + * + * @param exception + * @param context + * @throws Exception + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + log.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); + // 如果是某一个单元格的转换异常 能获取到具体行号 + // 如果要获取头的信息 配合invokeHeadMap使用 + if (exception instanceof ExcelDataConvertException) { + ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; + log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), + excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); + } + } + + /** + * 这里会一行行的返回头 + * + * @param headMap + * @param context + */ + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + log.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); + } + + @Override + public void invoke(ExceptionDemoData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraData.java new file mode 100644 index 0000000..1a56796 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraData.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.demo.read; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class DemoExtraData { + + private String row1; + + private String row2; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraListener.java new file mode 100644 index 0000000..ba1aa1f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoExtraListener.java @@ -0,0 +1,57 @@ +package ai.chat2db.excel.test.demo.read; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.metadata.CellExtra; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; + +/** + * 读取单元格的批注 + * + * @author Jiaju Zhuang + **/ +@Slf4j +public class DemoExtraListener implements ReadListener { + + @Override + public void invoke(DemoExtraData data, AnalysisContext context) {} + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + + @Override + public void extra(CellExtra extra, AnalysisContext context) { + log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra)); + switch (extra.getType()) { + case COMMENT: + log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), + extra.getColumnIndex(), + extra.getText()); + break; + case HYPERLINK: + if ("Sheet1!A1".equals(extra.getText())) { + log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), + extra.getColumnIndex(), extra.getText()); + } else if ("Sheet2!A1".equals(extra.getText())) { + log.info( + "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}," + + "内容是:{}", + extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), + extra.getLastColumnIndex(), extra.getText()); + } else { + Assertions.fail("Unknown hyperlink!"); + } + break; + case MERGE: + log.info( + "额外信息是合并单元格,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}", + extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), + extra.getLastColumnIndex()); + break; + default: + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoHeadDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoHeadDataListener.java new file mode 100644 index 0000000..9656647 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/DemoHeadDataListener.java @@ -0,0 +1,81 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.exception.ExcelDataConvertException; +import ai.chat2db.excel.metadata.data.ReadCellData; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 读取头 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DemoHeadDataListener implements ReadListener { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + /** + * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + * + * @param exception + * @param context + * @throws Exception + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + log.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); + if (exception instanceof ExcelDataConvertException) { + ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; + log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), + excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); + } + } + + /** + * 这里会一行行的返回头 + * + * @param headMap + * @param context + */ + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + log.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); + // 如果想转成成 Map + // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener + // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换 + } + + @Override + public void invoke(DemoData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ExceptionDemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ExceptionDemoData.java new file mode 100644 index 0000000..429b17f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ExceptionDemoData.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ExceptionDemoData { + /** + * 用日期去接字符串 肯定报错 + */ + private Date date; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameData.java new file mode 100644 index 0000000..7a65fea --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameData.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class IndexOrNameData { + /** + * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配 + */ + @ExcelProperty(index = 2) + private Double doubleData; + /** + * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 + */ + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameDataListener.java new file mode 100644 index 0000000..a6ab388 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/IndexOrNameDataListener.java @@ -0,0 +1,48 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class IndexOrNameDataListener extends AnalysisEventListener { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + @Override + public void invoke(IndexOrNameData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + cachedDataList.add(data); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/NoModelDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/NoModelDataListener.java new file mode 100644 index 0000000..4ed097e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/NoModelDataListener.java @@ -0,0 +1,49 @@ +package ai.chat2db.excel.test.demo.read; + +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 直接用map接收数据 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class NoModelDataListener extends AnalysisEventListener> { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + private List> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + @Override + public void invoke(Map data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + cachedDataList.add(data); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ReadTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ReadTest.java new file mode 100644 index 0000000..16ef6cd --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/read/ReadTest.java @@ -0,0 +1,337 @@ +package ai.chat2db.excel.test.demo.read; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.converters.DefaultConverterLoader; +import ai.chat2db.excel.enums.CellExtraTypeEnum; +import ai.chat2db.excel.read.listener.PageReadListener; +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.read.metadata.holder.csv.CsvReadWorkbookHolder; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +/** + * 读的常见写法 + * + * @author Jiaju Zhuang + */ + +@Slf4j +public class ReadTest { + + /** + * 最简单的读 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void simpleRead() { + // 写法1:JDK8+ ,不用额外写一个DemoDataListener + // since: 3.0.0-beta1 + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行 + // 具体需要返回多少行可以在`PageReadListener`的构造函数设置 + EasyExcel.read(fileName, DemoData.class, new PageReadListener(dataList -> { + for (DemoData demoData : dataList) { + log.info("读取到一条数据{}", JSON.toJSONString(demoData)); + } + })).sheet().doRead(); + + // 写法2: + // 匿名内部类 不用额外写一个DemoDataListener + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + EasyExcel.read(fileName, DemoData.class, new ReadListener() { + /** + * 单次缓存的数据量 + */ + public static final int BATCH_COUNT = 100; + /** + *临时存储 + */ + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + + @Override + public void invoke(DemoData data, AnalysisContext context) { + cachedDataList.add(data); + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + // 存储完成清理 list + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + log.info("存储数据库成功!"); + } + }).sheet().doRead(); + + // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 + // 写法3: + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); + + // 写法4 + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 一个文件一个reader + try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) { + // 构建一个sheet 这里可以指定名字或者no + ReadSheet readSheet = EasyExcel.readSheet(0).build(); + // 读取一个sheet + excelReader.read(readSheet); + } + } + + /** + * 指定列的下标或者列名 + * + *

+ * 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void indexOrNameRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里默认读取第一个sheet + EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead(); + } + + /** + * 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void repeatedRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 读取全部sheet + // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写 + EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll(); + + // 读取部分sheet + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + + // 写法1 + try (ExcelReader excelReader = EasyExcel.read(fileName).build()) { + // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener + ReadSheet readSheet1 = + EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + ReadSheet readSheet2 = + EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能 + excelReader.read(readSheet1, readSheet2); + } + } + + /** + * 日期、数字或者自定义格式转换 + *

+ * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} + *

+ * 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void converterRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener()) + // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。 + // 如果就想单个字段使用请使用@ExcelProperty 指定converter + // .registerConverter(new CustomStringStringConverter()) + // 读取sheet + .sheet().doRead(); + } + + /** + * 多行头 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *

+ * 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, + * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 + */ + @Test + public void complexHeaderRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet() + // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 + .headRowNumber(1).doRead(); + } + + /** + * 读取表头数据 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void headerRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); + } + + /** + * 额外信息(批注、超链接、合并单元格信息读取) + *

+ * 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息,所以只能最后通知哪些单元格有哪些额外信息 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoExtraData} + *

+ * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExtraListener} + *

+ * 3. 直接读即可 + * + * @since 2.2.0-beat1 + */ + @Test + public void extraRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "extra.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener()) + // 需要读取批注 默认不读取 + .extraRead(CellExtraTypeEnum.COMMENT) + // 需要读取超链接 默认不读取 + .extraRead(CellExtraTypeEnum.HYPERLINK) + // 需要读取合并单元格信息 默认不读取 + .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead(); + } + + /** + * 读取公式和单元格类型 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link CellDataReadDemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + * + * @since 2.2.0-beat1 + */ + @Test + public void cellDataRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead(); + } + + /** + * 数据转换等异常处理 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} + *

+ * 3. 直接读即可 + */ + @Test + public void exceptionRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, ExceptionDemoData.class, new DemoExceptionListener()).sheet().doRead(); + } + + /** + * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 + */ + @Test + public void synchronousRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish + List list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); + for (DemoData data : list) { + log.info("读取到数据:{}", JSON.toJSONString(data)); + } + + // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish + List> listMap = EasyExcel.read(fileName).sheet().doReadSync(); + for (Map data : listMap) { + // 返回每条数据的键值对 表示所在的列 和所在列的值 + log.info("读取到数据:{}", JSON.toJSONString(data)); + } + } + + /** + * 不创建对象的读 + */ + @Test + public void noModelRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 只要,然后读取第一个sheet 同步读取会自动finish + EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead(); + } + + /** + * 自定义修改csv配置 + */ + @Test + public void csvFormat() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.csv"; + try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) { + // 判断是 csv 文件 + if (excelReader.analysisContext().readWorkbookHolder() instanceof CsvReadWorkbookHolder) { + CsvReadWorkbookHolder csvReadWorkbookHolder = (CsvReadWorkbookHolder)excelReader.analysisContext() + .readWorkbookHolder(); + // 设置成逗号分隔 当然默认也是逗号分隔 + // 这里要注意 withDelimiter 会重新生成一个 所以要放回去 + csvReadWorkbookHolder.setCsvFormat(csvReadWorkbookHolder.getCsvFormat().withDelimiter(',')); + } + + // 拿到所有 sheet + List readSheetList = excelReader.excelExecutor().sheetList(); + // 如果只想读取第一个 咋样传入参数即可 + //ReadSheet readSheet = EasyExcel.readSheet(0).build(); + excelReader.read(readSheetList); + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/DownloadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/DownloadData.java new file mode 100644 index 0000000..c35a753 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/DownloadData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.demo.web; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DownloadData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/EasyexcelApplication.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/EasyexcelApplication.java new file mode 100644 index 0000000..74bd484 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/EasyexcelApplication.java @@ -0,0 +1,12 @@ +package ai.chat2db.excel.test.demo.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class EasyexcelApplication { + + public static void main(String[] args) { + SpringApplication.run(EasyexcelApplication.class, args); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDAO.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDAO.java new file mode 100644 index 0000000..6c0ed2e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDAO.java @@ -0,0 +1,18 @@ +package ai.chat2db.excel.test.demo.web; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +@Repository +public class UploadDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadData.java new file mode 100644 index 0000000..601c7c4 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadData.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.demo.web; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class UploadData { + private String string; + private Date date; + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDataListener.java new file mode 100644 index 0000000..4d9aa33 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/UploadDataListener.java @@ -0,0 +1,82 @@ +package ai.chat2db.excel.test.demo.web; + +import java.util.List; + +import ai.chat2db.excel.read.listener.ReadListener; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 +@Slf4j +public class UploadDataListener implements ReadListener { + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + private List cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private UploadDAO uploadDAO; + + public UploadDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + uploadDAO = new UploadDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param uploadDAO + */ + public UploadDataListener(UploadDAO uploadDAO) { + this.uploadDAO = uploadDAO; + } + + /** + * 这个每一条数据解析都会来调用 + * + * @param data one row value. It is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ + @Override + public void invoke(UploadData data, AnalysisContext context) { + log.info("解析到一条数据:{}", JSON.toJSONString(data)); + cachedDataList.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM + if (cachedDataList.size() >= BATCH_COUNT) { + saveData(); + // 存储完成清理 list + cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + } + + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 + saveData(); + log.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + log.info("{}条数据,开始存储数据库!", cachedDataList.size()); + uploadDAO.save(cachedDataList); + log.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/WebTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/WebTest.java new file mode 100644 index 0000000..083ae6c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/web/WebTest.java @@ -0,0 +1,111 @@ +package ai.chat2db.excel.test.demo.web; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.util.MapUtils; +import com.alibaba.fastjson2.JSON; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +/** + * web读写案例 + * + * @author Jiaju Zhuang + **/ +@Controller +public class WebTest { + + @Autowired + private UploadDAO uploadDAO; + + /** + * 文件下载(失败了会返回一个有部分数据的Excel) + *

+ * 1. 创建excel对应的实体对象 参照{@link DownloadData} + *

+ * 2. 设置返回的 参数 + *

+ * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 + */ + @GetMapping("download") + public void download(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + + EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); + } + + /** + * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel) + * + * @since 2.1.1 + */ + @GetMapping("downloadFailedUsingJson") + public void downloadFailedUsingJson(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman + try { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + // 这里需要设置不关闭流 + EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板") + .doWrite(data()); + } catch (Exception e) { + // 重置response + response.reset(); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + Map map = MapUtils.newHashMap(); + map.put("status", "failure"); + map.put("message", "下载文件失败" + e.getMessage()); + response.getWriter().println(JSON.toJSONString(map)); + } + } + + /** + * 文件上传 + *

+ * 1. 创建excel对应的实体对象 参照{@link UploadData} + *

+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} + *

+ * 3. 直接读即可 + */ + @PostMapping("upload") + @ResponseBody + public String upload(MultipartFile file) throws IOException { + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); + return "success"; + } + + private List data() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + DownloadData data = new DownloadData(); + data.setString("字符串" + 0); + data.setDate(new Date()); + data.setDoubleData(0.56); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CommentWriteHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CommentWriteHandler.java new file mode 100644 index 0000000..02f3324 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CommentWriteHandler.java @@ -0,0 +1,37 @@ +package ai.chat2db.excel.test.demo.write; + +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.write.handler.RowWriteHandler; +import ai.chat2db.excel.write.handler.context.RowWriteHandlerContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; + +/** + * 自定义拦截器.新增注释,第一行头加批注 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class CommentWriteHandler implements RowWriteHandler { + + @Override + public void afterRowDispose(RowWriteHandlerContext context) { + if (BooleanUtils.isTrue(context.getHead())) { + Sheet sheet = context.getWriteSheetHolder().getSheet(); + Drawing drawingPatriarch = sheet.createDrawingPatriarch(); + // 在第一行 第二列创建一个批注 + Comment comment = + drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short)1, 0, (short)2, 1)); + // 输入批注信息 + comment.setString(new XSSFRichTextString("创建批注!")); + // 将批注添加到单元格对象中 + sheet.getRow(0).getCell(1).setCellComment(comment); + } + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ComplexHeadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ComplexHeadData.java new file mode 100644 index 0000000..34139e7 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ComplexHeadData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 复杂头数据.这里最终效果是第一行就一个主标题,第二行分类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ComplexHeadData { + @ExcelProperty({"主标题", "字符串标题"}) + private String string; + @ExcelProperty({"主标题", "日期标题"}) + private Date date; + @ExcelProperty({"主标题", "数字标题"}) + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ConverterData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ConverterData.java new file mode 100644 index 0000000..3687c3a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ConverterData.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class ConverterData { + /** + * 我想所有的 字符串起前面加上"自定义:"三个字 + */ + @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class) + private String string; + /** + * 我想写到excel 用年月日的格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + @ExcelProperty("日期标题") + private Date date; + /** + * 我想写到excel 用百分比表示 + */ + @NumberFormat("#.##%") + @ExcelProperty(value = "数字标题") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomCellWriteHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomCellWriteHandler.java new file mode 100644 index 0000000..de2dedd --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomCellWriteHandler.java @@ -0,0 +1,34 @@ +package ai.chat2db.excel.test.demo.write; + +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Hyperlink; + +/** + * 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel + * + * @author Jiaju Zhuang + */ +@Slf4j +public class CustomCellWriteHandler implements CellWriteHandler { + + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + Cell cell = context.getCell(); + // 这里可以对cell进行任何操作 + log.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex()); + if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) { + CreationHelper createHelper = context.getWriteSheetHolder().getSheet().getWorkbook().getCreationHelper(); + Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL); + hyperlink.setAddress("https://github.com/alibaba/easyexcel"); + cell.setHyperlink(hyperlink); + } + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomSheetWriteHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomSheetWriteHandler.java new file mode 100644 index 0000000..23fe004 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomSheetWriteHandler.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.test.demo.write; + +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import ai.chat2db.excel.write.handler.context.SheetWriteHandlerContext; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.util.CellRangeAddressList; + +/** + * 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class CustomSheetWriteHandler implements SheetWriteHandler { + + @Override + public void afterSheetCreate(SheetWriteHandlerContext context) { + log.info("第{}个Sheet写入成功。", context.getWriteSheetHolder().getSheetNo()); + + // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行 + CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0); + DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper(); + DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"}); + DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList); + context.getWriteSheetHolder().getSheet().addValidationData(dataValidation); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomStringStringConverter.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomStringStringConverter.java new file mode 100644 index 0000000..40f088f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/CustomStringStringConverter.java @@ -0,0 +1,45 @@ +package ai.chat2db.excel.test.demo.write; + +import ai.chat2db.excel.converters.Converter; +import ai.chat2db.excel.converters.ReadConverterContext; +import ai.chat2db.excel.converters.WriteConverterContext; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.metadata.data.WriteCellData; + +/** + * String and string converter + * + * @author Jiaju Zhuang + */ +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 这里是读的时候会调用 不用管 + * + * @return + */ + @Override + public String convertToJavaData(ReadConverterContext context) { + return context.getReadCellData().getStringValue(); + } + + /** + * 这里是写的时候会调用 不用管 + * + * @return + */ + @Override + public WriteCellData convertToExcelData(WriteConverterContext context) { + return new WriteCellData<>("自定义:" + context.getValue()); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoData.java new file mode 100644 index 0000000..7445414 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoData.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DemoData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; + + /** + * 忽略这个字段 + */ + @ExcelIgnore + private String ignore; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoMergeData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoMergeData.java new file mode 100644 index 0000000..6cf3632 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoMergeData.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ContentLoopMerge; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 样式的数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +// 将第6-7行的2-3列合并成一个单元格 +// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2) +public class DemoMergeData { + // 这一列 每隔2行 合并单元格 + @ContentLoopMerge(eachRow = 2) + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoStyleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoStyleData.java new file mode 100644 index 0000000..560fdeb --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/DemoStyleData.java @@ -0,0 +1,47 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ContentFontStyle; +import ai.chat2db.excel.annotation.write.style.ContentStyle; +import ai.chat2db.excel.annotation.write.style.HeadFontStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 样式的数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +// 头背景设置成红色 IndexedColors.RED.getIndex() +@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10) +// 头字体设置成20 +@HeadFontStyle(fontHeightInPoints = 20) +// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex() +@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17) +// 内容字体设置成20 +@ContentFontStyle(fontHeightInPoints = 20) +public class DemoStyleData { + // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex() + @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14) + // 字符串的头字体设置成20 + @HeadFontStyle(fontHeightInPoints = 30) + // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex() + @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) + // 字符串的内容字体设置成20 + @ContentFontStyle(fontHeightInPoints = 30) + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDataWithAnnotation.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDataWithAnnotation.java new file mode 100644 index 0000000..8a4f4ee --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDataWithAnnotation.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.demo.write; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; + +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.converters.string.StringImageConverter; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 图片导出类 + */ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(100) +@ColumnWidth(100 / 8) +public class ImageDataWithAnnotation { + private File file; + private InputStream inputStream; + /** + * 如果string类型 必须指定转换器,string默认转换成string + */ + @ExcelProperty(converter = StringImageConverter.class) + private String string; + private byte[] byteArray; + /** + * 根据url导出 + * + * @since 2.1.1 + */ + private URL url; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDemoData.java new file mode 100644 index 0000000..054351e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/ImageDemoData.java @@ -0,0 +1,49 @@ +package ai.chat2db.excel.test.demo.write; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; + +import ai.chat2db.excel.converters.string.StringImageConverter; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.metadata.data.WriteCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 图片导出类 + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(100) +@ColumnWidth(100 / 8) +public class ImageDemoData { + private File file; + private InputStream inputStream; + /** + * 如果string类型 必须指定转换器,string默认转换成string + */ + @ExcelProperty(converter = StringImageConverter.class) + private String string; + private byte[] byteArray; + /** + * 根据url导出 + * + * @since 2.1.1 + */ + private URL url; + + /** + * 根据文件导出 并设置导出的位置。 + * + * @since 3.0.0-beta1 + */ + private WriteCellData writeCellDataFile; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/IndexData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/IndexData.java new file mode 100644 index 0000000..4018c54 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/IndexData.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class IndexData { + @ExcelProperty(value = "字符串标题", index = 0) + private String string; + @ExcelProperty(value = "日期标题", index = 1) + private Date date; + /** + * 这里设置3 会导致第二列空的 + */ + @ExcelProperty(value = "数字标题", index = 3) + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/LongestMatchColumnWidthData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/LongestMatchColumnWidthData.java new file mode 100644 index 0000000..9a115e0 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/LongestMatchColumnWidthData.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class LongestMatchColumnWidthData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题很长日期标题很长日期标题很长很长") + private Date date; + @ExcelProperty("数字") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WidthAndHeightData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WidthAndHeightData.java new file mode 100644 index 0000000..50b7998 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WidthAndHeightData.java @@ -0,0 +1,36 @@ +package ai.chat2db.excel.test.demo.write; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadRowHeight; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(10) +@HeadRowHeight(20) +@ColumnWidth(25) +public class WidthAndHeightData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + /** + * 宽度为50 + */ + @ColumnWidth(50) + @ExcelProperty("数字标题") + private Double doubleData; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteCellDemoData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteCellDemoData.java new file mode 100644 index 0000000..c55939b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteCellDemoData.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.test.demo.write; + +import ai.chat2db.excel.metadata.data.WriteCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 根据WriteCellData写 + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class WriteCellDemoData { + /** + * 超链接 + * + * @since 3.0.0-beta1 + */ + private WriteCellData hyperlink; + + /** + * 备注 + * + * @since 3.0.0-beta1 + */ + private WriteCellData commentData; + + /** + * 公式 + * + * @since 3.0.0-beta1 + */ + private WriteCellData formulaData; + + /** + * 指定单元格的样式。当然样式 也可以用注解等方式。 + * + * @since 3.0.0-beta1 + */ + private WriteCellData writeCellStyle; + + /** + * 指定一个单元格有多个样式 + * + * @since 3.0.0-beta1 + */ + private WriteCellData richText; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteTest.java new file mode 100644 index 0000000..97bac63 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/demo/write/WriteTest.java @@ -0,0 +1,771 @@ +package ai.chat2db.excel.test.demo.write; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.enums.CellDataTypeEnum; +import ai.chat2db.excel.test.core.head.ComplexHeadData; +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; +import ai.chat2db.excel.write.merge.LoopMergeStrategy; +import ai.chat2db.excel.write.style.HorizontalCellStyleStrategy; +import ai.chat2db.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.format.DateTimeFormat; +import ai.chat2db.excel.annotation.format.NumberFormat; +import ai.chat2db.excel.annotation.write.style.ColumnWidth; +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; +import ai.chat2db.excel.annotation.write.style.HeadRowHeight; +import ai.chat2db.excel.metadata.data.CommentData; +import ai.chat2db.excel.metadata.data.FormulaData; +import ai.chat2db.excel.metadata.data.HyperlinkData; +import ai.chat2db.excel.metadata.data.HyperlinkData.HyperlinkType; +import ai.chat2db.excel.metadata.data.ImageData; +import ai.chat2db.excel.metadata.data.ImageData.ImageType; +import ai.chat2db.excel.metadata.data.RichTextStringData; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.junit.jupiter.api.Test; + +/** + * 写的常见写法 + * + * @author Jiaju Zhuang + */ + +public class WriteTest { + + /** + * 最简单的写 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 直接写即可 + */ + @Test + public void simpleWrite() { + // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入 + + // 写法1 JDK8+ + // since: 3.0.0-beta1 + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData.class) + .sheet("模板") + .doWrite(() -> { + // 分页查询数据 + return data(); + }); + + // 写法2 + fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); + + // 写法3 + fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写 + try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { + WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); + excelWriter.write(data(), writeSheet); + } + } + + /** + * 根据参数只导出指定列 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 根据自己或者排除自己需要的列 + *

+ * 3. 直接写即可 + * + * @since 2.1.1 + */ + @Test + public void excludeOrIncludeWrite() { + String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。 + + // 根据用户传入字段 假设我们要忽略 date + Set excludeColumnFieldNames = new HashSet<>(); + excludeColumnFieldNames.add("date"); + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).excludeColumnFieldNames(excludeColumnFieldNames).sheet("模板") + .doWrite(data()); + + fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; + // 根据用户传入字段 假设我们只要导出 date + Set includeColumnFieldNames = new HashSet<>(); + includeColumnFieldNames.add("date"); + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).includeColumnFieldNames(includeColumnFieldNames).sheet("模板") + .doWrite(data()); + } + + /** + * 指定写入的列 + *

+ * 1. 创建excel对应的实体对象 参照{@link IndexData} + *

+ * 2. 使用{@link ExcelProperty}注解指定写入的列 + *

+ * 3. 直接写即可 + */ + @Test + public void indexWrite() { + String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data()); + } + + /** + * 复杂头写入 + *

+ * 1. 创建excel对应的实体对象 参照{@link ai.chat2db.excel.test.core.head.ComplexHeadData} + *

+ * 2. 使用{@link ExcelProperty}注解指定复杂的头 + *

+ * 3. 直接写即可 + */ + @Test + public void complexHeadWrite() { + String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, ai.chat2db.excel.test.core.head.ComplexHeadData.class).sheet("模板").doWrite(data()); + } + + /** + * 重复多次写入 + *

+ * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} + *

+ * 2. 使用{@link ExcelProperty}注解指定复杂的头 + *

+ * 3. 直接调用二次写入即可 + */ + @Test + public void repeatedWrite() { + // 方法1: 如果写到同一个sheet + String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写 + try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { + // 这里注意 如果同一个sheet只要创建一次 + WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来 + for (int i = 0; i < 5; i++) { + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + } + + // 方法2: 如果写到不同的sheet 同一个对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样 + WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + } + + // 方法3 如果写到不同的sheet 不同的对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) { + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class + // 实际上可以一直变 + WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + } + + } + + /** + * 日期、数字或者自定义格式转换 + *

+ * 1. 创建excel对应的实体对象 参照{@link ConverterData} + *

+ * 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *

+ * 3. 直接写即可 + */ + @Test + public void converterWrite() { + String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data()); + } + + /** + * 图片导出 + *

+ * 1. 创建excel对应的实体对象 参照{@link ImageDemoData} + *

+ * 2. 直接写即可 + */ + @Test + public void imageWrite() throws Exception { + String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx"; + + // 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1: + // 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接 + // 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片 + + String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; + try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) { + List list = ListUtils.newArrayList(); + ImageDemoData imageDemoData = new ImageDemoData(); + list.add(imageDemoData); + // 放入五种类型的图片 实际使用只要选一种即可 + imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); + imageDemoData.setFile(new File(imagePath)); + imageDemoData.setString(imagePath); + imageDemoData.setInputStream(inputStream); + imageDemoData.setUrl(new URL( + "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg")); + + // 这里演示 + // 需要额外放入文字 + // 而且需要放入2个图片 + // 第一个图片靠左 + // 第二个靠右 而且要额外的占用他后面的单元格 + WriteCellData writeCellData = new WriteCellData<>(); + imageDemoData.setWriteCellDataFile(writeCellData); + // 这里可以设置为 EMPTY 则代表不需要其他数据了 + writeCellData.setType(CellDataTypeEnum.STRING); + writeCellData.setStringValue("额外的放一些文字"); + + // 可以放入多个图片 + List imageDataList = new ArrayList<>(); + ImageData imageData = new ImageData(); + imageDataList.add(imageData); + writeCellData.setImageDataList(imageDataList); + // 放入2进制图片 + imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath))); + // 图片类型 + imageData.setImageType(ImageType.PICTURE_TYPE_PNG); + // 上 右 下 左 需要留空 + // 这个类似于 css 的 margin + // 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。 + imageData.setTop(5); + imageData.setRight(40); + imageData.setBottom(5); + imageData.setLeft(5); + + // 放入第二个图片 + imageData = new ImageData(); + imageDataList.add(imageData); + writeCellData.setImageDataList(imageDataList); + imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath))); + imageData.setImageType(ImageType.PICTURE_TYPE_PNG); + imageData.setTop(5); + imageData.setRight(5); + imageData.setBottom(5); + imageData.setLeft(50); + // 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格 + // 起点相对于当前单元格为0 当然可以不写 + imageData.setRelativeFirstRowIndex(0); + imageData.setRelativeFirstColumnIndex(0); + imageData.setRelativeLastRowIndex(0); + // 前面3个可以不写 下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格 + // 也就是说 这个图片会覆盖当前单元格和 后面的那一格 + imageData.setRelativeLastColumnIndex(1); + + // 写入数据 + EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list); + } + } + + /** + * 超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式 + *

+ * 1. 创建excel对应的实体对象 参照{@link WriteCellDemoData} + *

+ * 2. 直接写即可 + * + * @since 3.0.0-beta1 + */ + @Test + public void writeCellDataWrite() { + String fileName = TestFileUtil.getPath() + "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx"; + WriteCellDemoData writeCellDemoData = new WriteCellDemoData(); + + // 设置超链接 + WriteCellData hyperlink = new WriteCellData<>("官方网站"); + writeCellDemoData.setHyperlink(hyperlink); + HyperlinkData hyperlinkData = new HyperlinkData(); + hyperlink.setHyperlinkData(hyperlinkData); + hyperlinkData.setAddress("https://github.com/alibaba/easyexcel"); + hyperlinkData.setHyperlinkType(HyperlinkType.URL); + + // 设置备注 + WriteCellData comment = new WriteCellData<>("备注的单元格信息"); + writeCellDemoData.setCommentData(comment); + CommentData commentData = new CommentData(); + comment.setCommentData(commentData); + commentData.setAuthor("Jiaju Zhuang"); + commentData.setRichTextStringData(new RichTextStringData("这是一个备注")); + // 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格 + commentData.setRelativeLastColumnIndex(1); + commentData.setRelativeLastRowIndex(1); + + // 设置公式 + WriteCellData formula = new WriteCellData<>(); + writeCellDemoData.setFormulaData(formula); + FormulaData formulaData = new FormulaData(); + formula.setFormulaData(formulaData); + // 将 123456789 中的第一个数字替换成 2 + // 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用 + formulaData.setFormulaValue("REPLACE(123456789,1,1,2)"); + + // 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。 + WriteCellData writeCellStyle = new WriteCellData<>("单元格样式"); + writeCellStyle.setType(CellDataTypeEnum.STRING); + writeCellDemoData.setWriteCellStyle(writeCellStyle); + WriteCellStyle writeCellStyleData = new WriteCellStyle(); + writeCellStyle.setWriteCellStyle(writeCellStyleData); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色. + writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + + // 设置单个单元格多种样式 + // 这里需要设置 inMomery=true 不然会导致无法展示单个单元格多种样式,所以慎用 + WriteCellData richTest = new WriteCellData<>(); + richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING); + writeCellDemoData.setRichText(richTest); + RichTextStringData richTextStringData = new RichTextStringData(); + richTest.setRichTextStringDataValue(richTextStringData); + richTextStringData.setTextString("红色绿色默认"); + // 前2个字红色 + WriteFont writeFont = new WriteFont(); + writeFont.setColor(IndexedColors.RED.getIndex()); + richTextStringData.applyFont(0, 2, writeFont); + // 接下来2个字绿色 + writeFont = new WriteFont(); + writeFont.setColor(IndexedColors.GREEN.getIndex()); + richTextStringData.applyFont(2, 4, writeFont); + + List data = new ArrayList<>(); + data.add(writeCellDemoData); + EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data); + } + + /** + * 根据模板写入 + *

+ * 1. 创建excel对应的实体对象 参照{@link IndexData} + *

+ * 2. 使用{@link ExcelProperty}注解指定写入的列 + *

+ * 3. 使用withTemplate 写取模板 + *

+ * 4. 直接写即可 + */ + @Test + public void templateWrite() { + String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM + // 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入 + EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data()); + } + + /** + * 列宽、行高 + *

+ * 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData } + *

+ * 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度 + *

+ * 3. 直接写即可 + */ + @Test + public void widthAndHeightWrite() { + String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data()); + } + + /** + * 注解形式自定义样式 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoStyleData} + *

+ * 3. 直接写即可 + * + * @since 2.2.0-beta1 + */ + @Test + public void annotationStyleWrite() { + String fileName = TestFileUtil.getPath() + "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoStyleData.class).sheet("模板").doWrite(data()); + } + + /** + * 拦截器形式自定义样式 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 创建一个style策略 并注册 + *

+ * 3. 直接写即可 + */ + @Test + public void handlerStyleWrite() { + // 方法1 使用已有的策略 推荐 + // HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样 + // AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页 + String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class) + .registerWriteHandler(horizontalCellStyleStrategy) + .sheet("模板") + .doWrite(data()); + + // 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略 + // @since 3.0.0-beta2 + fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + EasyExcel.write(fileName, DemoData.class) + .registerWriteHandler(new CellWriteHandler() { + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + // 当前事件会在 数据设置到poi的cell里面才会回调 + // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true + if (BooleanUtils.isNotTrue(context.getHead())) { + // 第一个单元格 + // 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellData + WriteCellData cellData = context.getFirstCellData(); + // 这里需要去cellData 获取样式 + // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat + // ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了 + // 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回 + WriteCellStyle writeCellStyle = cellData.getOrCreateStyle(); + writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND + writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + + // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了 + } + } + }).sheet("模板") + .doWrite(data()); + + // 方法3: 使用poi的样式完全自己写 不推荐 + // @since 3.0.0-beta2 + // 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效 + // 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了 + fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + EasyExcel.write(fileName, DemoData.class) + .registerWriteHandler(new CellWriteHandler() { + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + // 当前事件会在 数据设置到poi的cell里面才会回调 + // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true + if (BooleanUtils.isNotTrue(context.getHead())) { + Cell cell = context.getCell(); + // 拿到poi的workbook + Workbook workbook = context.getWriteWorkbookHolder().getWorkbook(); + // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式 + // 不同单元格尽量传同一个 cellStyle + CellStyle cellStyle = workbook.createCellStyle(); + cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND + cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + cell.setCellStyle(cellStyle); + + // 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确 + + // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 + // cell里面去 会导致自己设置的不一样 + context.getFirstCellData().setWriteCellStyle(null); + } + } + }).sheet("模板") + .doWrite(data()); + } + + /** + * 合并单元格 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} {@link DemoMergeData} + *

+ * 2. 创建一个merge策略 并注册 + *

+ * 3. 直接写即可 + * + * @since 2.2.0-beta1 + */ + @Test + public void mergeWrite() { + // 方法1 注解 + String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; + // 在DemoStyleData里面加上ContentLoopMerge注解 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data()); + + // 方法2 自定义合并单元格策略 + fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; + // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写 + LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data()); + } + + /** + * 使用table去写入 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 然后写入table即可 + */ + @Test + public void tableWrite() { + String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; + // 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案 + // 这里 需要指定写用哪个class去写 + try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { + // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 + WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build(); + // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要 + WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build(); + WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet, writeTable0); + // 第二次写如也会创建头,然后在第一次的后面写入数据 + excelWriter.write(data(), writeSheet, writeTable1); + } + } + + /** + * 动态头,实时生成头写入 + *

+ * 思路是这样子的,先创建List头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 然后写入table即可 + */ + @Test + public void dynamicHeadWrite() { + String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx"; + EasyExcel.write(fileName) + // 这里放入动态头 + .head(head()).sheet("模板") + // 当然这里数据也可以用 List> 去传入 + .doWrite(data()); + } + + /** + * 自动列宽(不太精确) + *

+ * 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 {@link LongestMatchColumnWidthStyleStrategy} + * 重新实现. + *

+ * poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData} + *

+ * 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy} + *

+ * 3. 直接写即可 + */ + @Test + public void longestMatchColumnWidthWrite() { + String fileName = + TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, LongestMatchColumnWidthData.class) + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong()); + } + + /** + * 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个) + *

+ * demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler} + *

+ * 2. 直接写即可 + */ + @Test + public void customHandlerWrite() { + String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler()) + .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data()); + } + + /** + * 插入批注 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 注册拦截器 {@link CommentWriteHandler} + *

+ * 2. 直接写即可 + */ + @Test + public void commentWrite() { + String fileName = TestFileUtil.getPath() + "commentWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。 + EasyExcel.write(fileName, DemoData.class).inMemory(Boolean.TRUE).registerWriteHandler(new CommentWriteHandler()) + .sheet("模板").doWrite(data()); + } + + /** + * 可变标题处理(包括标题国际化等) + *

+ * 简单的说用List>的标题 但是还支持注解 + *

+ * 1. 创建excel对应的实体对象 参照{@link ConverterData} + *

+ * 2. 直接写即可 + */ + @Test + public void variableTitleWrite() { + // 写法1 + String fileName = TestFileUtil.getPath() + "variableTitleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, ConverterData.class).head(variableTitleHead()).sheet("模板").doWrite(data()); + } + + /** + * 不创建对象的写 + */ + @Test + public void noModelWrite() { + // 写法1 + String fileName = TestFileUtil.getPath() + "noModelWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); + } + + private List dataLong() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + LongestMatchColumnWidthData data = new LongestMatchColumnWidthData(); + data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i); + data.setDate(new Date()); + data.setDoubleData(1000000000000.0); + list.add(data); + } + return list; + } + + private List> variableTitleHead() { + List> list = ListUtils.newArrayList(); + List head0 = ListUtils.newArrayList(); + head0.add("string" + System.currentTimeMillis()); + List head1 = ListUtils.newArrayList(); + head1.add("number" + System.currentTimeMillis()); + List head2 = ListUtils.newArrayList(); + head2.add("date" + System.currentTimeMillis()); + list.add(head0); + list.add(head1); + list.add(head2); + return list; + } + + private List> head() { + List> list = ListUtils.newArrayList(); + List head0 = ListUtils.newArrayList(); + head0.add("字符串" + System.currentTimeMillis()); + List head1 = ListUtils.newArrayList(); + head1.add("数字" + System.currentTimeMillis()); + List head2 = ListUtils.newArrayList(); + head2.add("日期" + System.currentTimeMillis()); + list.add(head0); + list.add(head1); + list.add(head2); + return list; + } + + private List> dataList() { + List> list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + List data = ListUtils.newArrayList(); + data.add("字符串" + i); + data.add(0.56); + data.add(new Date()); + list.add(data); + } + return list; + } + + private List data() { + List list = ListUtils.newArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + i); + data.setDate(new Date()); + data.setDoubleData(0.56); + list.add(data); + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/CamlData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/CamlData.java new file mode 100644 index 0000000..605eb1e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/CamlData.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.temp; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * TODO + * + * @author 是仪 + */ +@Getter +@Setter +@EqualsAndHashCode +public class CamlData { + private String string1; + private String String2; + private String sTring3; + private String STring4; + private String STRING5; + private String STRing6; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData2.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData2.java new file mode 100644 index 0000000..dc07e9a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData2.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.temp; + +import java.math.BigDecimal; +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DemoData2 { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; + @ExcelProperty("数字标题2") + private BigDecimal bigDecimal; + /** + * 忽略这个字段 + */ + @ExcelIgnore + private String ignore; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData3.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData3.java new file mode 100644 index 0000000..8a2504f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/DemoData3.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.temp; + +import java.time.LocalDateTime; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DemoData3 { + @ExcelProperty("日期时间标题") + private LocalDateTime localDateTime; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/FillTempTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/FillTempTest.java new file mode 100644 index 0000000..beb73c2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/FillTempTest.java @@ -0,0 +1,129 @@ +package ai.chat2db.excel.test.temp; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.test.demo.fill.FillData; +import ai.chat2db.excel.test.temp.fill.FillData2; +import ai.chat2db.excel.write.merge.OnceAbsoluteMergeStrategy; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import org.junit.jupiter.api.Test; + +/** + * 写的填充写法 + * + * @author Jiaju Zhuang + * @since 2.1.1 + */ + +public class FillTempTest { + + /** + * 复杂的填充 + * + * @since 2.1.1 + */ + @Test + public void complexFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(2, 2, 0, 1); + + String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).registerWriteHandler(onceAbsoluteMergeStrategy) + .withTemplate(TestFileUtil.readUserHomeFile("test/simple.xlsx")).build(); + WriteSheet writeSheet0 = EasyExcel.writerSheet(0).build(); + WriteSheet writeSheet1 = EasyExcel.writerSheet(1).build(); + + excelWriter.fill(teamp(), writeSheet0); + excelWriter.fill(teamp(), writeSheet1); + + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet0); + + excelWriter.finish(); + } + + /** + * 数据量大的复杂填充 + *

+ * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 + */ + @Test + public void complexFillWithTable() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + // 这里模板 删除了list以后的数据,也就是统计的这一行 + String templateFileName = "D:\\test\\complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 直接写入数据 + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data2(), writeSheet); + + // 写入list之前的数据 + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // list 后面还有个统计 想办法手动写入 + // 这里偷懒直接用list 也可以用对象 + List> totalListList = new ArrayList>(); + List totalList = new ArrayList(); + totalListList.add(totalList); + totalList.add(null); + totalList.add(null); + totalList.add(null); + // 第四列 + totalList.add("统计:1000"); + // 这里是write 别和fill 搞错了 + excelWriter.write(totalListList, writeSheet); + excelWriter.finish(); + // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 + // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 + } + + private List data2() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData2 fillData = new FillData2(); + list.add(fillData); + fillData.setTest("ttttttt" + i); + } + return list; + } + + private List teamp() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + TempFillData fillData = new TempFillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Lock2Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Lock2Test.java new file mode 100644 index 0000000..a3e686c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Lock2Test.java @@ -0,0 +1,445 @@ +package ai.chat2db.excel.test.temp; + +import java.io.File; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.data.ReadCellData; +import ai.chat2db.excel.test.demo.write.DemoData; +import ai.chat2db.excel.util.PositionUtils; +import ai.chat2db.excel.write.style.HorizontalCellStyleStrategy; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Slf4j +public class Lock2Test { + + private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class); + + @Test + public void test() throws Exception { + // File file = TestFileUtil.readUserHomeFile("test/test4.xlsx"); + // File file = TestFileUtil.readUserHomeFile("test/test6.xls"); + File file = new File("/Users/zhuangjiaju/IdeaProjects/easyexcel/src/test/resources/converter/converter07.xlsx"); + + List list = EasyExcel.read( + "/Users/zhuangjiaju/Downloads/证券投资基金估值表_外贸信托-稳盈淳享37号集合资金信托计划_2024-07-23(1).xls") + //.useDefaultListener(false) + .sheet(0) + .headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (Object data : list) { + LOGGER.info("返回数据:{}", CollectionUtils.size(data)); + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test33() throws Exception { + File file = TestFileUtil.readUserHomeFile("test/test6.xlsx"); + + EasyExcel.read(file, LockData.class, new LockDataListener()).sheet(0).headRowNumber(0) + .doRead(); + + } + + @Test + public void write() throws Exception { + String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") + .doWrite(data()); + } + + @Test + public void simpleWrite() { + String fileName = TestFileUtil.getPath() + System.currentTimeMillis() + ".xlsx"; + System.out.println(fileName); + EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = new ArrayList(); + head0.add("表头"); + + list.add(head0); + return list; + } + + private List> dataList() { + List> list = new ArrayList>(); + List data = new ArrayList(); + data.add("字符串"); + data.add(new Date()); + data.add(0.56); + list.add(data); + return list; + } + + @Test + public void testc() throws Exception { + LOGGER.info("reslut:{}", JSON.toJSONString(new CellReference("B3"))); + } + + @Test + public void simpleRead() { + // 写法1: + String fileName = "D:\\test\\珠海 (1).xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + EasyExcel.read(fileName, LockData.class, new LockDataListener()).useDefaultListener(false).sheet().doRead(); + } + + @Test + public void test2() throws Exception { + File file = new File("D:\\test\\converter03.xls"); + + List list = EasyExcel.read(file).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + LOGGER.info("文件状态:{}", file.exists()); + file.delete(); + Thread.sleep(500 * 1000); + } + + @Test + public void test335() throws Exception { + + LOGGER.info("reslut:{}", PositionUtils.getCol("A10", null)); + LOGGER.info("reslut:{}", PositionUtils.getRow("A10")); + LOGGER.info("reslut:{}", PositionUtils.getCol("AB10", null)); + LOGGER.info("reslut:{}", PositionUtils.getRow("AB10")); + + //LOGGER.info("reslut:{}", PositionUtils2.getCol("A10",null)); + //LOGGER.info("reslut:{}", PositionUtils2.getRow("A10")); + //LOGGER.info("reslut:{}", PositionUtils2.getCol("AB10",null)); + //LOGGER.info("reslut:{}", PositionUtils2.getRow("AB10")); + } + + @Test + public void numberforamt() throws Exception { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44727.99998842592), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + // + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44728.99998842592), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + // + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44729.99998836806), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + // + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44727.99998842592).setScale(10, RoundingMode + // .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + // + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44728.99998842592).setScale(10, RoundingMode + // .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + + //44729.9999883681 + //44729.999988368058 + //LOGGER.info("date:{}", + // NumberDataFormatterUtils.format(BigDecimal.valueOf(44729.999988368058).setScale(10, RoundingMode + // .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss", + // null, + // null, null)); + //LOGGER.info("date:{}",BigDecimal.valueOf(44729.999988368058).setScale(10, RoundingMode.HALF_UP).doubleValue + // ()); + + // 2022/6/17 23:59:59 + // 期望 44729.99998842592 + //LOGGER.info("data:{}", DateUtil.getJavaDate(44729.9999883681, true)); + LOGGER.info("data4:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058) + .setScale(4, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data5:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058) + .setScale(5, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data6:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058) + .setScale(6, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data7:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058) + .setScale(7, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data8:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058) + .setScale(8, RoundingMode.HALF_UP).doubleValue(), false)); + + LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.999988368058, false))); + LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.9999883681, false))); + + LOGGER.info("data:{}", DateUtil.getJavaDate(Double.parseDouble("44729.999988368058"), false)); + LOGGER.info("data:{}", DateUtil.getJavaDate(Double.parseDouble("44729.9999883681"), false)); + + // 44729.999976851854 + // 44729.999988368058 + LOGGER.info("data:{}", DateUtil.getExcelDate(format.parse("2022-06-17 23:59:58"))); + // 44729.99998842592 + LOGGER.info("data:{}", DateUtil.getExcelDate(format.parse("2022-06-17 23:59:59"))); + + LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999976851854) + .setScale(10, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.99998842592) + .setScale(10, RoundingMode.HALF_UP).doubleValue(), false)); + + LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999976851854) + .setScale(5, RoundingMode.HALF_UP).doubleValue(), false)); + LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.99998842592) + .setScale(5, RoundingMode.HALF_UP).doubleValue(), false)); + } + + @Test + public void testDate() throws Exception { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + log.info("TT:{}", format.format(new Date(100L))); + log.info("TT:{}", new Date().getTime()); + } + + @Test + public void testDateAll() throws Exception { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + long dateTime = 0L; + while (true) { + Date date = new Date(dateTime); + double excelDate = DateUtil.getExcelDate(date); + + Assertions.assertEquals("测试基本转换错误" + dateTime, format.format(date), + format.format(DateUtil.getJavaDate(excelDate, false))); + Assertions.assertEquals("测试精度5转换错误" + dateTime, format.format(date), + format.format(DateUtil.getJavaDate(BigDecimal.valueOf(excelDate) + .setScale(10, RoundingMode.HALF_UP).doubleValue(), false))); + LOGGER.info("date:{}", format2.format(DateUtil.getJavaDate(BigDecimal.valueOf(excelDate) + .setScale(10, RoundingMode.HALF_UP).doubleValue()))); + dateTime += 1000L; + // 30天输出 + if (dateTime % (24 * 60 * 60 * 1000) == 0) { + log.info("{}成功", format.format(date)); + } + if (dateTime > 1673957544750L) { + log.info("结束啦"); + break; + } + } + log.info("结束啦"); + + } + + @Test + public void numberforamt3() throws Exception { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + List> list = EasyExcel.read("/Users/zhuangjiaju/Downloads/date3.xlsx") + .useDefaultListener(false) + .sheet(0) + .headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (Map readCellDataMap : list) { + ReadCellData data = readCellDataMap.get(0); + LOGGER.info("data:{}", format.format( + DateUtil.getJavaDate(data.getNumberValue().setScale(10, RoundingMode.HALF_UP).doubleValue(), false))); + + } + // + //LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44727.999988425923, false))); + //LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.999988368058, false))); + + } + + @Test + public void numberforamt4() throws Exception { + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData.class) + .sheet("模板") + .doWrite(() -> { + // 分页查询数据 + return data2(); + }); + + } + + @Test + public void numberforamt77() throws Exception { + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData3.class) + .sheet("模板") + .doWrite(() -> { + List list = new ArrayList<>(); + DemoData3 demoData3 = new DemoData3(); + demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 400000000)); + list.add(demoData3); + demoData3 = new DemoData3(); + demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 499000000)); + list.add(demoData3); + demoData3 = new DemoData3(); + demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 500000000)); + list.add(demoData3); + demoData3 = new DemoData3(); + demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 501000000)); + list.add(demoData3); + demoData3 = new DemoData3(); + demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 995000000)); + list.add(demoData3); + return list; + }); + + } + + @Test + public void numberforamt99() throws Exception { + LocalDateTime localDateTime = LocalDateTime.of(2023, 1, 1, 0, 0, 0, 995000000); + log.info("date:{}", localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))); + + } + + @Test + public void numberforamt5() throws Exception { + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData.class) + .sheet("模板") + .doWrite(() -> { + // 分页查询数据 + return data3(); + }); + + } + + @Test + public void numberforamt6() throws Exception { + DecimalFormat decimalFormat = new DecimalFormat("#.#"); + BigDecimal bigDecimal = new BigDecimal(3101011021236149800L); + log.info("b:{}", bigDecimal); + log.info("b:{}", bigDecimal.setScale(-4, RoundingMode.HALF_UP)); + log.info("b:{}", decimalFormat.format(bigDecimal.setScale(-4, RoundingMode.HALF_UP))); + + } + + @Test + public void numberforamt7() throws Exception { + DecimalFormat decimalFormat = new DecimalFormat("#.#"); + BigDecimal bigDecimal = new BigDecimal(3.1010110212361498E+18).round(new MathContext(15, RoundingMode.HALF_UP)); + //bigDecimal. + + // bigDecimal + log.info("b:{}", bigDecimal); + log.info("b:{}", bigDecimal.setScale(-4, RoundingMode.HALF_UP)); + log.info("b:{}", decimalFormat.format(bigDecimal.setScale(-4, RoundingMode.HALF_UP))); + log.info("b:{}", decimalFormat.format(bigDecimal)); + + } + + private List data3() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + DemoData2 data = new DemoData2(); + data.setString("字符串" + i); + data.setDoubleData(0.56); + data.setBigDecimal(BigDecimal.valueOf(3101011021236149800L)); + list.add(data); + } + return list; + } + + private List data() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + i); + try { + data.setDate(format.parse("2032-01-18 09:00:01.995")); + } catch (ParseException e) { + throw new RuntimeException(e); + } + data.setDoubleData(0.56); + list.add(data); + } + return list; + } + + private List data2() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + i); + try { + data.setDate(format.parse("2032-01-18 09:00:00.")); + } catch (ParseException e) { + throw new RuntimeException(e); + } + data.setDoubleData(0.56); + list.add(data); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockData.java new file mode 100644 index 0000000..ede00b3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockData.java @@ -0,0 +1,29 @@ +package ai.chat2db.excel.test.temp; + +import ai.chat2db.excel.annotation.format.NumberFormat; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class LockData { + @NumberFormat("#.##%") + private Double string0; + private String string1; + private String string2; + private String string3; + private String string4; + private String string5; + private String string6; + private String string7; + private String string8; + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockDataListener.java new file mode 100644 index 0000000..df007e9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockDataListener.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.test.temp; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.context.AnalysisContext; +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.demo.read.DemoDataListener; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class LockDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + List list = new ArrayList(); + + @Override + public void invoke(LockData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + list.add(data); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + LOGGER.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockTest.java new file mode 100644 index 0000000..5bb70ea --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/LockTest.java @@ -0,0 +1,43 @@ +package ai.chat2db.excel.test.temp; + +import java.io.FileInputStream; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class LockTest { + private static final Logger LOGGER = LoggerFactory.getLogger(LockTest.class); + + @Test + public void test() throws Exception { + List list = + EasyExcel.read(new FileInputStream("/Users/zhuangjiaju/Downloads/-0304.2.xlsx")).useDefaultListener(false) + .doReadAllSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test2() throws Exception { + List list = + EasyExcel.read(new FileInputStream("D:\\test\\开发部.xls")).sheet().headRowNumber(0).doReadSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", ((Map)data).size()); + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleData.java new file mode 100644 index 0000000..a722ada --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleData.java @@ -0,0 +1,23 @@ +package ai.chat2db.excel.test.temp; + +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class StyleData { + private byte[] byteValue; + private Byte[] byteValue2; + private byte byteValue1; + private Byte byteValue4; + private byte byteValue3; + private String[] ss; + private List s1s; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleTest.java new file mode 100644 index 0000000..35a0f74 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/StyleTest.java @@ -0,0 +1,196 @@ +package ai.chat2db.excel.test.temp; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; + +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.crypt.Decryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.BuiltinFormats; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class StyleTest { + private static final Logger LOGGER = LoggerFactory.getLogger(StyleTest.class); + + @Test + public void test() { + List list = EasyExcel.read("D:\\test\\styleTest.xls").sheet().headRowNumber(0).doReadSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void poi() throws Exception { + InputStream is = new FileInputStream("D:\\test\\styleTest.xls"); + HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is); + HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(0); + HSSFRow hssfRow = hssfSheet.getRow(0); + System.out.println(hssfRow.getCell(0).getCellStyle().getDataFormatString()); + DataFormatter formatter = new DataFormatter(); + System.out.println(hssfRow.getCell(0).getNumericCellValue()); + System.out.println(hssfRow.getCell(1).getNumericCellValue()); + System.out.println(hssfRow.getCell(2).getNumericCellValue()); + System.out.println(hssfRow.getCell(0).getCellStyle().getDataFormatString()); + System.out.println(hssfRow.getCell(1).getCellStyle().getDataFormatString()); + System.out.println(hssfRow.getCell(2).getCellStyle().getDataFormatString()); + + } + + @Test + public void poi07() throws Exception { + InputStream is = new FileInputStream("D:\\test\\styleTest.xlsx"); + Workbook workbook = WorkbookFactory.create(is); // 这种方式 Excel 2003/2007/2010 都是可以处理的 + Sheet sheet = workbook.getSheetAt(0); + Row hssfRow = sheet.getRow(0); + System.out.println(hssfRow.getCell(0).getCellStyle().getDataFormatString()); + DataFormatter formatter = new DataFormatter(); + System.out.println(hssfRow.getCell(0).getNumericCellValue()); + System.out.println(hssfRow.getCell(1).getNumericCellValue()); + System.out.println(hssfRow.getCell(2).getNumericCellValue()); + System.out.println(hssfRow.getCell(0).getCellStyle().getDataFormat()); + System.out.println(hssfRow.getCell(1).getCellStyle().getDataFormat()); + System.out.println(hssfRow.getCell(2).getCellStyle().getDataFormat()); + System.out.println(hssfRow.getCell(3).getCellStyle().getDataFormat()); + System.out.println(hssfRow.getCell(0).getCellStyle().getDataFormatString()); + System.out.println(hssfRow.getCell(1).getCellStyle().getDataFormatString()); + System.out.println(hssfRow.getCell(2).getCellStyle().getDataFormatString()); + System.out.println(hssfRow.getCell(3).getCellStyle().getDataFormatString()); + isDate(hssfRow.getCell(0)); + isDate(hssfRow.getCell(1)); + isDate(hssfRow.getCell(2)); + isDate(hssfRow.getCell(3)); + + } + + @Test + public void poi0701() throws Exception { + InputStream is = new FileInputStream("D:\\test\\f1.xlsx"); + Workbook workbook = WorkbookFactory.create(is); + Sheet sheet = workbook.getSheetAt(0); + print(sheet.getRow(0).getCell(0)); + print(sheet.getRow(1).getCell(0)); + print(sheet.getRow(2).getCell(0)); + print(sheet.getRow(3).getCell(0)); + } + + @Test + public void poi0702() throws Exception { + Workbook workbook = WorkbookFactory.create(new FileInputStream("D:\\test\\t2.xlsx")); + workbook = WorkbookFactory.create(new File("D:\\test\\t2.xlsx")); + Sheet sheet = workbook.getSheetAt(0); + Row row = sheet.getRow(0); + System.out.println(row.getCell(0).getNumericCellValue()); + } + + @Test + public void poi0703() throws Exception { + try { + POIFSFileSystem poifsFileSystem = new POIFSFileSystem(new FileInputStream("D:\\test\\t2.xlsx")); + System.out.println(poifsFileSystem.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + POIFSFileSystem poifsFileSystem = new POIFSFileSystem(new File("D:\\test\\t2.xlsx")); + System.out.println(poifsFileSystem.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)); + } catch (Exception e) { + e.printStackTrace(); + } + try { + POIFSFileSystem poifsFileSystem = new POIFSFileSystem(new FileInputStream("D:\\test\\t222.xlsx")); + System.out.println(poifsFileSystem.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)); + } catch (Exception e) { + e.printStackTrace(); + } + try { + POIFSFileSystem poifsFileSystem = new POIFSFileSystem(new File("D:\\test\\t222.xlsx")); + System.out.println(poifsFileSystem.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void print(Cell cell) { + System.out.println( + DateUtil.isADateFormat(cell.getCellStyle().getDataFormat(), cell.getCellStyle().getDataFormatString())); + System.out.println(cell.getCellStyle().getDataFormat()); + System.out.println(cell.getCellStyle().getDataFormatString()); + DataFormatter f = new DataFormatter(); + System.out.println(f.formatCellValue(cell)); + if (cell.getCellStyle().getDataFormatString() != null) { + + } + ExcelStyleDateFormatter ff = new ExcelStyleDateFormatter(cell.getCellStyle().getDataFormatString()); + + } + + @Test + public void testFormatter() throws Exception { + ExcelStyleDateFormatter ff = new ExcelStyleDateFormatter("yyyy年m月d日"); + + System.out.println(ff.format(new Date())); + } + + @Test + public void testFormatter2() throws Exception { + StyleData styleData = new StyleData(); + Field field = styleData.getClass().getDeclaredField("byteValue"); + LOGGER.info("field:{}", field.getType().getName()); + field = styleData.getClass().getDeclaredField("byteValue2"); + LOGGER.info("field:{}", field.getType().getName()); + field = styleData.getClass().getDeclaredField("byteValue4"); + LOGGER.info("field:{}", field.getType()); + field = styleData.getClass().getDeclaredField("byteValue3"); + LOGGER.info("field:{}", field.getType()); + } + + @Test + public void testFormatter3() throws Exception { + LOGGER.info("field:{}", Byte.class == Byte.class); + } + + private void isDate(Cell cell) { + System.out.println( + DateUtil.isADateFormat(cell.getCellStyle().getDataFormat(), cell.getCellStyle().getDataFormatString())); + //System.out.println(HSSFDateUtil.isCellDateFormatted(cell)); + DataFormatter f = new DataFormatter(); + System.out.println(f.formatCellValue(cell)); + + } + + @Test + public void testBuiltinFormats() throws Exception { + System.out.println(BuiltinFormats.getBuiltinFormat(48)); + System.out.println(BuiltinFormats.getBuiltinFormat(57)); + System.out.println(BuiltinFormats.getBuiltinFormat(28)); + + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/TempFillData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/TempFillData.java new file mode 100644 index 0000000..a89f185 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/TempFillData.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.test.temp; + +import ai.chat2db.excel.annotation.write.style.ContentRowHeight; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@ContentRowHeight(30) +public class TempFillData { + private String name; + private double number; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteLargeTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteLargeTest.java new file mode 100644 index 0000000..23e9449 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteLargeTest.java @@ -0,0 +1,199 @@ +package ai.chat2db.excel.test.temp; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.read.listener.PageReadListener; +import ai.chat2db.excel.test.core.large.LargeData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; +import ai.chat2db.excel.write.style.HorizontalCellStyleStrategy; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; +import org.apache.poi.hssf.eventusermodel.HSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFRequest; +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SSTRecord; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +@Slf4j +public class WriteLargeTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteLargeTest.class); + + @Test + public void test() throws Exception { + // 方法2 如果写到不同的sheet 同一个对象 + String fileName = TestFileUtil.getPath() + "large" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + ExcelWriter excelWriter = EasyExcel.write(fileName, LargeData.class).registerWriteHandler( + horizontalCellStyleStrategy).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 100; j++) { + excelWriter.write(data(), writeSheet); + LOGGER.info("{} fill success.", j); + } + excelWriter.finish(); + + } + + @Test + public void read() throws Exception { + log.info("start"); + String fileName = "/Users/zhuangjiaju/Downloads/1e9e0578a9634abbbbd9b67f338f142a.xls"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行 + // 具体需要返回多少行可以在`PageReadListener`的构造函数设置 + EasyExcel.read(fileName, new PageReadListener>>(dataList -> { + log.info("SIZEL:{}", dataList.size()); + })).sheet().doRead(); + + log.info("test"); + + } + + @Test + public void read2() throws Exception { + // 使用输入的文件创建一个新的文件输入流 + //FileInputStream fin = new FileInputStream("/Users/zhuangjiaju/Downloads/1e9e0578a9634abbbbd9b67f338f142a + // .xls"); + // 创建一个新的org.apache.poi.poifs.filesystem.Filesystem + POIFSFileSystem poifs = new POIFSFileSystem( + new File("/Users/zhuangjiaju/Downloads/1e9e0578a9634abbbbd9b67f338f142a.xls")); + // 在InputStream中获取Workbook流 + InputStream din = poifs.createDocumentInputStream("Workbook"); + // 构造出HSSFRequest对象 + HSSFRequest req = new HSSFRequest(); + // 注册全部的监听器 + req.addListenerForAllRecords(new EventExample()); + // 创建事件工厂 + HSSFEventFactory factory = new HSSFEventFactory(); + // 根据文档输入流处理我们监听的事件 + factory.processEvents(req, din); + // 关闭文件输入流 + //fin.close(); + // 关闭文档输入流 + din.close(); + System.out.println("读取结束"); + } + + @Test + public void read3() throws Exception { + HSSFWorkbook hwb = new HSSFWorkbook( + new FileInputStream("/Users/zhuangjiaju/Downloads/1e9e0578a9634abbbbd9b67f338f142a.xls")); + HSSFSheet sheet = hwb.getSheetAt(0); + HSSFRow row = null; + HSSFCell cell = null; + for (int i = sheet.getFirstRowNum(); i <= sheet.getPhysicalNumberOfRows(); i++) { + row = sheet.getRow(i); + if(row!=null){ + log.info("r:{}",row.getRowNum()); + + } + } + + log.info("end"); + } + + public static class EventExample implements HSSFListener { + private SSTRecord sstrec; + + /** + * 此方法监听传入记录并根据需要处理它们 + * + * @param record读取时找到的记录 + */ + public void processRecord(Record record) { + switch (record.getSid()) { + //BOFRecord可以表示工作表或工作簿的开头 + case BOFRecord.sid: + BOFRecord bof = (BOFRecord)record; + if (bof.getType() == bof.TYPE_WORKBOOK) { + System.out.println("监听到工作表"); + } else if (bof.getType() == bof.TYPE_WORKSHEET) { + System.out.println("监听到工作簿"); + } + break; + case BoundSheetRecord.sid: + BoundSheetRecord bsr = (BoundSheetRecord)record; + System.out.println("工作簿名称: " + bsr.getSheetname()); + break; + } + } + } + + @Test + public void test2() throws Exception { + // 方法2 如果写到不同的sheet 同一个对象 + String fileName = TestFileUtil.getPath() + "large" + System.currentTimeMillis() + ".xlsx"; + + ExcelWriter excelWriter = EasyExcel.write(fileName, LargeData.class).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 100; j++) { + excelWriter.write(data(), writeSheet); + LOGGER.info("{} fill success.", j); + } + excelWriter.finish(); + + } + + private List> data() { + List> list = new ArrayList<>(); + + for (int j = 0; j < 10000; j++) { + List oneRow = new ArrayList<>(); + for (int i = 0; i < 150; i++) { + oneRow.add("这是测试字段" + i); + } + list.add(oneRow); + } + + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV33Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV33Test.java new file mode 100644 index 0000000..a5d2c8e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV33Test.java @@ -0,0 +1,148 @@ +package ai.chat2db.excel.test.temp; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.demo.write.DemoData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.util.BooleanUtils; +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.handler.context.CellWriteHandlerContext; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class WriteV33Test { + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteV33Test.class); + + @Test + public void handlerStyleWrite() { + // 方法1 使用已有的策略 推荐 + // HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样 + // AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页 + String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + //// 头的策略 + //WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + //// 背景设置为红色 + //headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + //WriteFont headWriteFont = new WriteFont(); + //headWriteFont.setFontHeightInPoints((short)20); + //headWriteCellStyle.setWriteFont(headWriteFont); + //// 内容的策略 + //WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + //// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + //contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + //// 背景绿色 + //contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + //WriteFont contentWriteFont = new WriteFont(); + //// 字体大小 + //contentWriteFont.setFontHeightInPoints((short)20); + //contentWriteCellStyle.setWriteFont(contentWriteFont); + //// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + //HorizontalCellStyleStrategy horizontalCellStyleStrategy = + // new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + // + //// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + //EasyExcel.write(fileName, DemoData.class) + // .registerWriteHandler(horizontalCellStyleStrategy) + // .sheet("模板") + // .doWrite(data()); + // + // 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略 + //fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + //EasyExcel.write(fileName, DemoData.class) + // .registerWriteHandler(new CellWriteHandler() { + // @Override + // public void afterCellDispose(CellWriteHandlerContext context) { + // // 当前事件会在 数据设置到poi的cell里面才会回调 + // // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true + // if (BooleanUtils.isNotTrue(context.getHead())) { + // // 第一个单元格 + // // 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellData + // WriteCellData cellData = context.getFirstCellData(); + // // 这里需要去cellData 获取样式 + // // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat + // // ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了 + // // 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回 + // WriteCellStyle writeCellStyle = cellData.getOrCreateStyle(); + // writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + // // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND + // writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // + // // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了 + // } + // } + // }).sheet("模板") + // .doWrite(data()); + + // 方法3: 使用poi的样式完全自己写 不推荐 + // 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效 + // 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了 + fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + EasyExcel.write(fileName, DemoData.class) + .registerWriteHandler(new CellWriteHandler() { + @Override + public void afterCellDispose(CellWriteHandlerContext context) { + // 当前事件会在 数据设置到poi的cell里面才会回调 + // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true + if (BooleanUtils.isNotTrue(context.getHead())) { + Cell cell = context.getCell(); + // 拿到poi的workbook + Workbook workbook = context.getWriteWorkbookHolder().getWorkbook(); + // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式 + // 不同单元格尽量传同一个 cellStyle + CellStyle cellStyle = workbook.createCellStyle(); + cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND + cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + cell.setCellStyle(cellStyle); + + // 由于这里没有指定datafrmat 所以格式化出来的数据需要 + + // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 + // cell里面去 会导致自己设置的不一样 + context.getFirstCellData().setWriteCellStyle(null); + } + } + }).sheet("模板") + .doWrite(data()); + } + + private List data() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + i); + data.setDate(new Date()); + data.setDoubleData(0.56); + list.add(data); + } + return list; + } + + + @Test + public void test4() throws Exception{ + Path path= Files.createTempFile(new File("/Users/zhuangjiaju/test/test0422/test/xx").toPath(),System.currentTimeMillis()+"",".jpg"); + System.out.println(path); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV34Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV34Test.java new file mode 100644 index 0000000..0b55c43 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/WriteV34Test.java @@ -0,0 +1,88 @@ +package ai.chat2db.excel.test.temp; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.demo.write.DemoData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.style.WriteCellStyle; +import ai.chat2db.excel.write.metadata.style.WriteFont; +import ai.chat2db.excel.write.style.HorizontalCellStyleStrategy; + +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class WriteV34Test { + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteV34Test.class); + + @Test + public void test() throws Exception { + String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).head(head()).registerWriteHandler(horizontalCellStyleStrategy).sheet( + "模板") + .doWrite(data(1)); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = new ArrayList(); + head0.add("字符串" + System.currentTimeMillis()); + head0.add("再找找"); + List head1 = new ArrayList(); + head1.add("数字" + System.currentTimeMillis()); + List head2 = new ArrayList(); + head2.add("日期" + System.currentTimeMillis()); + List head3 = new ArrayList(); + head3.add("日期" + System.currentTimeMillis()); + list.add(head0); + list.add(head1); + list.add(head2); + list.add(head3); + + return list; + } + + private List data(int no) { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("字符串" + no + "---" + i); + list.add(data); + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Xls03Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Xls03Test.java new file mode 100644 index 0000000..4a73ed3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/Xls03Test.java @@ -0,0 +1,51 @@ +package ai.chat2db.excel.test.temp; + +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.support.cglib.beans.BeanMap; +import ai.chat2db.excel.support.cglib.core.DebuggingClassWriter; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class Xls03Test { + private static final Logger LOGGER = LoggerFactory.getLogger(Xls03Test.class); + + @Test + public void test() { + List list = EasyExcel.read("D:\\test\\8.xls").sheet().doReadSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test2() { + System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); + System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, + "/Users/zhuangjiaju/IdeaProjects/easyexcel/target"); + + CamlData camlData = new CamlData(); + //camlData.setTest("test2"); + //camlData.setAEst("test3"); + //camlData.setTEST("test4"); + + BeanMap beanMap = BeanMapUtils.create(camlData); + + LOGGER.info("test:{}", beanMap.get("test")); + LOGGER.info("test:{}", beanMap.get("Test")); + LOGGER.info("test:{}", beanMap.get("TEst")); + LOGGER.info("test:{}", beanMap.get("TEST")); + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/DataType.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/DataType.java new file mode 100644 index 0000000..8fce4f2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/DataType.java @@ -0,0 +1,38 @@ +package ai.chat2db.excel.test.temp.bug; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author jiaosong + * @desc + * @date 2021/4/6 + */ +@Getter +@Setter +@EqualsAndHashCode +public class DataType { + /** + * 任务id + */ + @ExcelProperty("任务ID") + private Integer id; + + @ExcelProperty("多余字段1") + private String firstSurplus; + + @ExcelProperty("多余字段2") + private String secSurplus; + + @ExcelProperty("多余字段3") + private String thirdSurplus; + + @ExcelProperty(value = "备注1") + private String firstRemark; + + @ExcelProperty(value = "备注2") + private String secRemark; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/ExcelCreat.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/ExcelCreat.java new file mode 100644 index 0000000..5b23b81 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/ExcelCreat.java @@ -0,0 +1,37 @@ +package ai.chat2db.excel.test.temp.bug; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.Collections; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.write.metadata.WriteSheet; + +/** + * @author jiaosong + * @desc + * @date 2020/4/6 + */ +public class ExcelCreat { + + public static void main(String[] args) throws FileNotFoundException { + List data = getData(); + ExcelWriter excelWriter = null; + excelWriter = EasyExcel.write(new FileOutputStream("all.xlsx")).build(); + WriteSheet writeSheet = EasyExcel.writerSheet(1, "test") + .head(HeadType.class) + .build(); + excelWriter.write(data, writeSheet); + excelWriter.finish(); + } + + private static List getData() { + DataType vo = new DataType(); + vo.setId(738); + vo.setFirstRemark("1222"); + vo.setSecRemark("22222"); + return Collections.singletonList(vo); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/HeadType.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/HeadType.java new file mode 100644 index 0000000..7c37f2b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/bug/HeadType.java @@ -0,0 +1,31 @@ +package ai.chat2db.excel.test.temp.bug; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author jiaosong + * @desc + * @date 2021/4/6 + */ +@Getter +@Setter +@EqualsAndHashCode +public class HeadType { + + /** + * 任务id + */ + @ExcelProperty("任务ID") + private Integer id; + + @ExcelProperty(value = "备注1") + private String firstRemark; + + @ExcelProperty(value = "备注2") + private String secRemark; + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/cache/CacheTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/cache/CacheTest.java new file mode 100644 index 0000000..c2061b4 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/cache/CacheTest.java @@ -0,0 +1,51 @@ +package ai.chat2db.excel.test.temp.cache; + +import java.io.File; +import java.util.HashMap; +import java.util.UUID; + +import ai.chat2db.excel.test.temp.poi.Poi2Test; +import ai.chat2db.excel.util.FileUtils; +import com.alibaba.fastjson2.JSON; + +import org.ehcache.Cache; +import org.ehcache.PersistentCacheManager; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.CacheManagerBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; +import org.ehcache.config.units.MemoryUnit; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + **/ +public class CacheTest { + private static final Logger LOGGER = LoggerFactory.getLogger(Poi2Test.class); + + @Test + public void cache() throws Exception { + + File readTempFile = FileUtils.createCacheTmpFile(); + + File cacheFile = new File(readTempFile.getPath(), UUID.randomUUID().toString()); + PersistentCacheManager persistentCacheManager = + CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(cacheFile)) + .withCache("cache", CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, HashMap.class, + ResourcePoolsBuilder.newResourcePoolsBuilder().disk(10, MemoryUnit.GB))) + .build(true); + Cache cache = persistentCacheManager.getCache("cache", Integer.class, HashMap.class); + + HashMap map = new HashMap(); + map.put(1, "test"); + + cache.put(1, map); + LOGGER.info("dd1:{}", JSON.toJSONString(cache.get(1))); + + cache.clear(); + + LOGGER.info("dd2:{}", JSON.toJSONString(cache.get(1))); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvData.java new file mode 100644 index 0000000..ad91bc2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvData.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.test.temp.csv; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * TODO + * + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class CsvData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; + /** + * 忽略这个字段 + */ + @ExcelIgnore + private String ignore; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvDataListeer.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvDataListeer.java new file mode 100644 index 0000000..1dcafba --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvDataListeer.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.temp.csv; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CsvDataListeer extends AnalysisEventListener { + @Override + public void invoke(CsvData data, AnalysisContext context) { + log.info("data:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvReadTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvReadTest.java new file mode 100644 index 0000000..87f6d97 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/csv/CsvReadTest.java @@ -0,0 +1,110 @@ +package ai.chat2db.excel.test.temp.csv; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.util.TestFileUtil; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.csv.CSVRecord; +import org.apache.poi.poifs.filesystem.FileMagic; +import org.junit.jupiter.api.Test; + +@Slf4j +public class CsvReadTest { + + @Test + public void write() throws Exception { + Appendable out = new PrintWriter( + new OutputStreamWriter(new FileOutputStream(TestFileUtil.createNewFile("csvWrite1.csv")))); + CSVPrinter printer = CSVFormat.DEFAULT.withHeader("userId", "userName") + .print(out); + for (int i = 0; i < 10; i++) { + printer.printRecord("userId" + i, "userName" + i); + } + printer.flush(); + printer.close(); + } + + @Test + public void read1() throws Exception { + Iterable records = CSVFormat.DEFAULT.withNullString("").parse( + new FileReader("/Users/zhuangjiaju/IdeaProjects/easyexcel/target/test-classes/t1.csv")); + for (CSVRecord record : records) { + String lastName = record.get(0); + String firstName = record.get(1); + log.info("row:{},{}", lastName, firstName); + } + + } + + @Test + public void csvWrite() throws Exception { + // 写法1 + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".csv"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, CsvData.class).sheet().doWrite(data()); + + // 读 + List list = EasyExcel.read(fileName).sheet(0).headRowNumber(0).doReadSync(); + log.info("数据:{}", list.size()); + for (Object data : list) { + log.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void writev2() throws Exception { + // 写法1 + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".csv"; + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, CsvData.class).sheet().doWrite(data()); + + EasyExcel.read(fileName, CsvData.class, new CsvDataListeer()).sheet().doRead(); + } + + @Test + public void writeFile() throws Exception { + FileInputStream fileInputStream = new FileInputStream(new File("/Users/zhuangjiaju/test/test1.csv")); + FileMagic fileMagic = FileMagic.valueOf(fileInputStream); + log.info("{}", fileMagic); + } + + private List data() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + CsvData data = new CsvData(); + data.setString("字符,串" + i); + //data.setDate(new Date()); + data.setDoubleData(0.56); + data.setIgnore("忽略" + i); + list.add(data); + } + return list; + } + + @Test + public void read() { + // + //Iterable records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(in); + //for (CSVRecord record : records) { + // String lastName = record.get("id"); + // String firstName = record.get("name"); + // System.out.println(lastName); + // System.out.println(firstName); + //} + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/DataType.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/DataType.java new file mode 100644 index 0000000..56e7993 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/DataType.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.temp.data; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class DataType { + /** + * 任务id + */ + @ExcelProperty("任务ID") + private Integer id; + + @ExcelProperty("多余字段1") + private String firstSurplus; + + @ExcelProperty("多余字段2") + private String secSurplus; + + @ExcelProperty("多余字段3") + private String thirdSurplus; + + @ExcelProperty(value = "备注1") + private String firstRemark; + + @ExcelProperty(value = "备注2") + private String secRemark; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/HeadType.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/HeadType.java new file mode 100644 index 0000000..3cf00ae --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/data/HeadType.java @@ -0,0 +1,26 @@ +package ai.chat2db.excel.test.temp.data; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class HeadType { + + /** + * 任务id + */ + @ExcelProperty("任务ID") + private Integer id; + + @ExcelProperty(value = "备注1") + private String firstRemark; + + @ExcelProperty(value = "备注2") + private String secRemark; + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatData.java new file mode 100644 index 0000000..13a52e9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatData.java @@ -0,0 +1,20 @@ +package ai.chat2db.excel.test.temp.dataformat; + +import ai.chat2db.excel.metadata.data.ReadCellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * TODO + * + * @author 罗成 + **/ +@Getter +@Setter +@EqualsAndHashCode +public class DataFormatData { + private ReadCellData date; + private ReadCellData num; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatTest.java new file mode 100644 index 0000000..5dd5cda --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatTest.java @@ -0,0 +1,189 @@ +package ai.chat2db.excel.test.temp.dataformat; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.core.dataformat.DateFormatData; +import ai.chat2db.excel.test.temp.Lock2Test; +import ai.chat2db.excel.test.util.TestFileUtil; +import com.alibaba.fastjson2.JSON; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 格式测试 + * + * @author Jiaju Zhuang + **/ + +public class DataFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class); + + @Test + public void test() throws Exception { + File file = new File("D:\\test\\dataformat.xlsx"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Short dataFormat = data.getDate().getDataFormatData().getIndex(); + + String dataFormatString = data.getDate().getFormulaData().getFormulaValue(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void testxls() throws Exception { + File file = new File("D:\\test\\dataformat.xls"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Short dataFormat = data.getDate().getDataFormatData().getIndex(); + + String dataFormatString = data.getDate().getFormulaData().getFormulaValue(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test3() throws IOException { + String file = "D:\\test\\dataformat1.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + Cell cell = xssfSheet.getRow(0).getCell(0); + DataFormatter d = new DataFormatter(); + System.out.println(d.formatCellValue(cell)); + } + + @Test + public void test31() throws IOException { + System.out.println(DateUtil.isADateFormat(181, "[DBNum1][$-404]m\"\u6708\"d\"\u65e5\";@")); + } + + @Test + public void test43() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("yyyy'年'm'月'd'日' h'点'mm'哈哈哈m'"); + System.out.println(s.format(new Date())); + } + + @Test + public void test463() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("[$-804]yyyy年m月"); + System.out.println(s.format(new Date())); + } + + @Test + public void test1() throws Exception { + System.out.println(DateUtil.isADateFormat(181, "yyyy\"年啊\"m\"月\"d\"日\"\\ h")); + System.out.println(DateUtil.isADateFormat(180, "yyyy\"年\"m\"月\"d\"日\"\\ h\"点\"")); + } + + @Test + public void test2() throws Exception { + List list1 = new ArrayList(3000); + long start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1.clear(); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1 = new ArrayList(3000); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + } + + @Test + public void test355() throws IOException, InvalidFormatException { + File file = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx"); + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + DataFormatter d = new DataFormatter(Locale.CHINA); + + for (int i = 0; i < xssfSheet.getLastRowNum(); i++) { + Row row = xssfSheet.getRow(i); + System.out.println(d.formatCellValue(row.getCell(0))); + } + + } + + @Test + public void test3556() throws IOException, InvalidFormatException { + String file = "D://test/dataformat1.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + DataFormatter d = new DataFormatter(Locale.CHINA); + + for (int i = 0; i < xssfSheet.getLastRowNum(); i++) { + Row row = xssfSheet.getRow(i); + System.out.println(d.formatCellValue(row.getCell(0))); + } + + } + + @Test + public void tests() throws IOException, InvalidFormatException { + SimpleDateFormat s1 = new SimpleDateFormat("yyyy\"5E74\"m\"6708\"d\"65E5\""); + System.out.println(s1.format(new Date())); + s1 = new SimpleDateFormat("yyyy年m月d日"); + System.out.println(s1.format(new Date())); + } + + @Test + public void tests1() throws IOException, InvalidFormatException { + String file = "D://test/dataformat1.xlsx"; + List list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync(); + for (DateFormatData data : list) { + LOGGER.info("返回:{}", JSON.toJSONString(data)); + } + } + + @Test + public void tests3() throws IOException, InvalidFormatException { + SimpleDateFormat s1 = new SimpleDateFormat("ah\"时\"mm\"分\""); + System.out.println(s1.format(new Date())); + } + + private static final Pattern date_ptrn6 = Pattern.compile("^.*(年|月|日|时|分|秒)+.*$"); + + @Test + public void tests34() throws IOException, InvalidFormatException { + System.out.println(date_ptrn6.matcher("2017但是").matches()); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatter1.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatter1.java new file mode 100644 index 0000000..81b865c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/dataformat/DataFormatter1.java @@ -0,0 +1,1287 @@ +/* + * ==================================================================== Licensed to the Apache Software Foundation (ASF) + * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff + * can be found in svn at location root/projects/3rd-party/src + * ==================================================================== + */ +package ai.chat2db.excel.test.temp.dataformat; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ai.chat2db.excel.analysis.ExcelAnalyserImpl; +import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; +import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat; +import org.apache.poi.ss.usermodel.ExcelNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.FormulaError; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.FractionFormat; +import org.apache.poi.ss.util.DateFormatConverter; +import org.apache.poi.ss.util.NumberToTextConverter; +import org.apache.poi.util.LocaleUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DataFormatter contains methods for formatting the value stored in an Cell. This can be useful for reports and GUI + * presentations when you need to display data exactly as it appears in Excel. Supported formats include currency, SSN, + * percentages, decimals, dates, phone numbers, zip codes, etc. + *

+ * Internally, formats will be implemented using subclasses of {@link Format} such as {@link DecimalFormat} and + * {@link java.text.SimpleDateFormat}. Therefore the formats used by this class must obey the same pattern rules as + * these Format subclasses. This means that only legal number pattern characters ("0", "#", ".", "," etc.) may appear in + * number formats. Other characters can be inserted before or after the number pattern to form a + * prefix or suffix. + *

+ *

+ * For example the Excel pattern "$#,##0.00 "USD"_);($#,##0.00 "USD")" + * will be correctly formatted as "$1,000.00 USD" or "($1,000.00 USD)". However the pattern + * "00-00-00" is incorrectly formatted by DecimalFormat as "000000--". For Excel formats that are not + * compatible with DecimalFormat, you can provide your own custom {@link Format} implementation via + * DataFormatter.addFormat(String,Format). The following custom formats are already provided by this class: + *

+ * + *
+ * 
  • SSN "000-00-0000"
  • + *
  • Phone Number "(###) ###-####"
  • + *
  • Zip plus 4 "00000-0000"
  • + *
+ *
+ *

+ * If the Excel format pattern cannot be parsed successfully, then a default format will be used. The default number + * format will mimic the Excel General format: "#" for whole numbers and "#.##########" for decimal numbers. You can + * override the default format pattern with + * DataFormatter.setDefaultNumberFormat(Format). Note: the default format will only be used when a Format + * cannot be created from the cell's data format string. + * + *

+ * Note that by default formatted numeric values are trimmed. Excel formats can contain spacers and padding and the + * default behavior is to strip them off. + *

+ *

+ * Example: + *

+ *

+ * Consider a numeric cell with a value 12.343 and format "##.##_ ". The trailing underscore + * and space ("_ ") in the format adds a space to the end and Excel formats this cell as "12.34 ", but + * DataFormatter trims the formatted value and returns "12.34". + *

+ * You can enable spaces by passing the emulateCSV=true flag in the DateFormatter cosntructor. + * If set to true, then the output tries to conform to what you get when you take an xls or xlsx in Excel and Save As + * CSV file: + *
    + *
  • returned values are not trimmed
  • + *
  • Invalid dates are formatted as 255 pound signs ("#")
  • + *
  • simulate Excel's handling of a format string of all # when the value is 0. Excel will output "", + * DataFormatter will output "0". + *
+ *

+ * Some formats are automatically "localized" by Excel, eg show as mm/dd/yyyy when loaded in Excel in some Locales but + * as dd/mm/yyyy in others. These are always returned in the "default" (US) format, as stored in the file. Some format + * strings request an alternate locale, eg [$-809]d/m/yy h:mm AM/PM which explicitly requests UK locale. + * These locale directives are (currently) ignored. You can use {@link DateFormatConverter} to do some of this + * localisation if you need it. + */ +public class DataFormatter1 implements Observer { + private static final String defaultFractionWholePartFormat = "#"; + private static final String defaultFractionFractionPartFormat = "#/##"; + /** + * Pattern to find a number format: "0" or "#" + */ + private static final Pattern numPattern = Pattern.compile("[0#]+"); + + /** + * Pattern to find days of week as text "ddd...." + */ + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + + /** + * Pattern to find "AM/PM" marker + */ + private static final Pattern amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE); + + /** + * Pattern to find formats with condition ranges e.g. [>=100] + */ + private static final Pattern rangeConditionalPattern = + Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); + + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into + * locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); + + /** + * A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red, + * White, Yellow, "Color n" (1<=n<=56) + */ + private static final Pattern colorPattern = Pattern.compile("(\\[BLACK\\])|(\\[BLUE\\])|(\\[CYAN\\])|(\\[GREEN\\])|" + + "(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" + + "(\\[COLOR\\s*\\d\\])|(\\[COLOR\\s*[0-5]\\d\\])", Pattern.CASE_INSENSITIVE); + + /** + * A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called + */ + private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*\\/\\s*([#\\d]+)"); + + /** + * A regex to strip junk out of fraction formats + */ + private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ \\?#\\d\\/]+)"); + + /** + * A regex to detect if an alternate grouping character is used in a numeric format + */ + private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); + + /** + * Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs + * ("#"). + */ + private static final String invalidDateTimeString; + + static { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 255; i++) {buf.append('#');} + invalidDateTimeString = buf.toString(); + } + + /** + * The decimal symbols of the locale used for formatting values. + */ + private DecimalFormatSymbols decimalSymbols; + + /** + * The date symbols of the locale used for formatting values. + */ + private DateFormatSymbols dateSymbols; + + /** + * A default date format, if no date format was given + */ + private DateFormat defaultDateformat; + + /** + * General format for numbers. + */ + private Format generalNumberFormat; + + /** + * A default format to use when a number pattern cannot be parsed. + */ + private Format defaultNumFormat; + + /** + * A map to cache formats. Map formats + */ + private final Map formats = new HashMap(); + + private final boolean emulateCSV; + + /** + * stores the locale valid it the last formatting call + */ + private Locale locale; + + /** + * stores if the locale should change according to {@link LocaleUtil#getUserLocale()} + */ + private boolean localeIsAdapting; + + private class LocaleChangeObservable extends Observable { + void checkForLocaleChange() { + checkForLocaleChange(LocaleUtil.getUserLocale()); + } + + void checkForLocaleChange(Locale newLocale) { + if (!localeIsAdapting) {return;} + if (newLocale.equals(locale)) {return;} + super.setChanged(); + notifyObservers(newLocale); + } + } + + /** + * the Observable to notify, when the locale has been changed + */ + private final LocaleChangeObservable localeChangedObservable = new LocaleChangeObservable(); + + /** + * For logging any problems we find + */ + private static final Logger logger = LoggerFactory.getLogger(ExcelAnalyserImpl.class); + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + */ + public DataFormatter1() { + this(false); + } + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + * + * @param emulateCSV whether to emulate CSV output. + */ + public DataFormatter1(boolean emulateCSV) { + this(LocaleUtil.getUserLocale(), true, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + */ + public DataFormatter1(Locale locale) { + this(locale, false); + } + + /** + * Creates a formatter using the given locale. + * + * @param emulateCSV whether to emulate CSV output. + */ + public DataFormatter1(Locale locale, boolean emulateCSV) { + this(locale, false, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + * + * @param localeIsAdapting (true only if locale is not user-specified) + * @param emulateCSV whether to emulate CSV output. + */ + private DataFormatter1(Locale locale, boolean localeIsAdapting, boolean emulateCSV) { + this.localeIsAdapting = true; + localeChangedObservable.addObserver(this); + // localeIsAdapting must be true prior to this first checkForLocaleChange call. + localeChangedObservable.checkForLocaleChange(locale); + // set localeIsAdapting so subsequent checks perform correctly + // (whether a specific locale was provided to this DataFormatter or DataFormatter should + // adapt to the current user locale as the locale changes) + this.localeIsAdapting = localeIsAdapting; + this.emulateCSV = emulateCSV; + } + + /** + * Return a Format for the given cell if one exists, otherwise try to create one. This method will return + * null if the any of the following is true: + *

    + *
  • the cell's style is null
  • + *
  • the style's data format string is null or empty
  • + *
  • the format string cannot be recognized as either a number or date
  • + *
+ * + * @param cell The cell to retrieve a Format for + * @return A Format for the format String + */ + private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + if (cell == null) {return null;} + + ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); + + if (numFmt == null) { + return null; + } + + int formatIndex = numFmt.getIdx(); + String formatStr = numFmt.getFormat(); + if (formatStr == null || formatStr.trim().length() == 0) { + return null; + } + return getFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format getFormat(double cellValue, int formatIndex, String formatStrIn) { + localeChangedObservable.checkForLocaleChange(); + + // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. + // That however would require other code to be re factored. + // String[] formatBits = formatStrIn.split(";"); + // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; + // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; + + String formatStr = formatStrIn; + + // Excel supports 2+ part conditional data formats, eg positive/negative/zero, + // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds + // of different formats for different ranges, just +ve/-ve, we need to + // handle these ourselves in a special way. + // For now, if we detect 2+ parts, we call out to CellFormat to handle it + // TODO Going forward, we should really merge the logic between the two classes + if (formatStr.contains(";") && (formatStr.indexOf(';') != formatStr.lastIndexOf(';') + || rangeConditionalPattern.matcher(formatStr).matches())) { + try { + // Ask CellFormat to get a formatter for it + CellFormat cfmt = CellFormat.getInstance(locale, formatStr); + // CellFormat requires callers to identify date vs not, so do so + Object cellValueO = Double.valueOf(cellValue); + if (DateUtil.isADateFormat(formatIndex, formatStr) && + // don't try to handle Date value 0, let a 3 or 4-part format take care of it + ((Double)cellValueO).doubleValue() != 0.0) { + cellValueO = DateUtil.getJavaDate(cellValue); + } + // Wrap and return (non-cachable - CellFormat does that) + return new CellFormatResultWrapper(cfmt.apply(cellValueO)); + } catch (Exception e) { + logger.warn("Formatting failed for format {}, falling back", formatStr, e); + } + } + + // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. + if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { + formatStr = formatStr.replaceAll("#", ""); + } + + // See if we already have it cached + Format format = formats.get(formatStr); + if (format != null) { + return format; + } + + // Is it one of the special built in types, General or @? + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + // Build a formatter, and cache it + format = createFormat(cellValue, formatIndex, formatStr); + formats.put(formatStr, format); + return format; + } + + /** + * Create and return a Format based on the format string from a cell's style. If the pattern cannot be parsed, + * return a default pattern. + * + * @param cell The Excel cell + * @return A Format representing the excel format. May return null. + */ + public Format createFormat(Cell cell) { + + int formatIndex = cell.getCellStyle().getDataFormat(); + String formatStr = cell.getCellStyle().getDataFormatString(); + return createFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format createFormat(double cellValue, int formatIndex, String sFormat) { + localeChangedObservable.checkForLocaleChange(); + + String formatStr = sFormat; + + // Remove colour formatting if present + Matcher colourM = colorPattern.matcher(formatStr); + while (colourM.find()) { + String colour = colourM.group(); + + // Paranoid replacement... + int at = formatStr.indexOf(colour); + if (at == -1) {break;} + String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); + if (nFormatStr.equals(formatStr)) {break;} + + // Try again in case there's multiple + formatStr = nFormatStr; + colourM = colorPattern.matcher(formatStr); + } + + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); + while (m.find()) { + String match = m.group(); + String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); + if (symbol.indexOf('$') > -1) { + symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + + symbol.substring(symbol.indexOf('$'), symbol.length()); + } + formatStr = m.replaceAll(symbol); + m = localePatternGroup.matcher(formatStr); + } + + // Check for special cases + if (formatStr == null || formatStr.trim().length() == 0) { + return getDefaultFormat(cellValue); + } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + if ("".equals("") || (DateUtil.isADateFormat(formatIndex, formatStr) && DateUtil.isValidExcelDate(cellValue))) { + return createDateFormat(formatStr, cellValue); + } + // Excel supports fractions in format strings, which Java doesn't + if (formatStr.contains("#/") || formatStr.contains("?/")) { + String[] chunks = formatStr.split(";"); + for (String chunk1 : chunks) { + String chunk = chunk1.replaceAll("\\?", "#"); + Matcher matcher = fractionStripper.matcher(chunk); + chunk = matcher.replaceAll(" "); + chunk = chunk.replaceAll(" +", " "); + Matcher fractionMatcher = fractionPattern.matcher(chunk); + // take the first match + if (fractionMatcher.find()) { + String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat; + return new FractionFormat(wholePart, fractionMatcher.group(3)); + } + } + + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in + // fractions. + // String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", + // "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); + // System.out.println("formatStr: "+strippedFormatStr); + return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); + } + + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr, cellValue); + } + + if (emulateCSV) { + return new ConstantStringFormat(cleanFormatForNumber(formatStr)); + } + // TODO - when does this occur? + return null; + } + + private Format createDateFormat(String pFormatStr, double cellValue) { + String formatStr = pFormatStr; + formatStr = formatStr.replaceAll("\\\\-", "-"); + formatStr = formatStr.replaceAll("\\\\,", ","); + formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char + formatStr = formatStr.replaceAll("\\\\ ", " "); + formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll(";@", ""); + formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy + formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting + formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates + + boolean hasAmPm = false; + Matcher amPmMatcher = amPmPattern.matcher(formatStr); + while (amPmMatcher.find()) { + formatStr = amPmMatcher.replaceAll("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + Matcher dateMatcher = daysAsText.matcher(formatStr); + if (dateMatcher.find()) { + String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E"); + formatStr = dateMatcher.replaceAll(match); + } + + // Convert excel date format to SimpleDateFormat. + // Excel uses lower and upper case 'm' for both minutes and months. + // From Excel help: + /* + The "m" or "mm" code must appear immediately after the "h" or"hh" + code or immediately before the "ss" code; otherwise, Microsoft + Excel displays the month instead of minutes." + */ + + StringBuilder sb = new StringBuilder(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + boolean isElapsed = false; + for (int j = 0; j < chars.length; j++) { + char c = chars[j]; + if (c == '\'') { + sb.append(c); + j++; + + // skip until the next quote + while (j < chars.length) { + c = chars[j]; + sb.append(c); + if (c == '\'') { + break; + } + j++; + } + } else if (c == '[' && !isElapsed) { + isElapsed = true; + mIsMonth = false; + sb.append(c); + } else if (c == ']' && isElapsed) { + isElapsed = false; + sb.append(c); + } else if (isElapsed) { + if (c == 'h' || c == 'H') { + sb.append('H'); + } else if (c == 'm' || c == 'M') { + sb.append('m'); + } else if (c == 's' || c == 'S') { + sb.append('s'); + } else { + sb.append(c); + } + } else if (c == 'h' || c == 'H') { + mIsMonth = false; + if (hasAmPm) { + sb.append('h'); + } else { + sb.append('H'); + } + } else if (c == 'm' || c == 'M') { + if (mIsMonth) { + sb.append('M'); + ms.add(Integer.valueOf(sb.length() - 1)); + } else { + sb.append('m'); + } + } else if (c == 's' || c == 'S') { + sb.append('s'); + // if 'M' precedes 's' it should be minutes ('m') + for (int index : ms) { + if (sb.charAt(index) == 'M') { + sb.replace(index, index + 1, "m"); + } + } + mIsMonth = true; + ms.clear(); + } else if (Character.isLetter(c)) { + mIsMonth = true; + ms.clear(); + if (c == 'y' || c == 'Y') { + sb.append('y'); + } else if (c == 'd' || c == 'D') { + sb.append('d'); + } else { + sb.append(c); + } + } else { + if (Character.isWhitespace(c)) { + ms.clear(); + } + sb.append(c); + } + } + formatStr = sb.toString(); + + try { + return new ExcelStyleDateFormatter(formatStr, dateSymbols); + } catch (IllegalArgumentException iae) { + logger.debug("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + + } + + private String cleanFormatForNumber(String formatStr) { + StringBuilder sb = new StringBuilder(formatStr); + + if (emulateCSV) { + // Requested spacers with "_" are replaced by a single space. + // Full-column-width padding "*" are removed. + // Not processing fractions at this time. Replace ? with space. + // This matches CSV output. + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*' || c == '?') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (c == '?') { + sb.setCharAt(i, ' '); + } else if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + if (c == '_') { + sb.setCharAt(i + 1, ' '); + } else { + sb.deleteCharAt(i + 1); + } + // Remove the character too + sb.deleteCharAt(i); + i--; + } + } + } + } else { + // If they requested spacers, with "_", + // remove those as we don't do spacing + // If they requested full-column-width + // padding, with "*", remove those too + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + sb.deleteCharAt(i + 1); + } + // Remove the _ too + sb.deleteCharAt(i); + i--; + } + } + } + + // Now, handle the other aspects like + // quoting and scientific notation + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + // remove quotes and back slashes + if (c == '\\' || c == '"') { + sb.deleteCharAt(i); + i--; + + // for scientific/engineering notation + } else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') { + sb.deleteCharAt(i); + i--; + } + } + + return sb.toString(); + } + + private static class InternalDecimalFormatWithScale extends Format { + + private static final Pattern endsWithCommas = Pattern.compile("(,+)$"); + private BigDecimal divider; + private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private final DecimalFormat df; + + private static final String trimTrailingCommas(String s) { + return s.replaceAll(",+$", ""); + } + + public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) { + df = new DecimalFormat(trimTrailingCommas(pattern), symbols); + setExcelStyleRoundingMode(df); + Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern); + if (endsWithCommasMatcher.find()) { + String commas = (endsWithCommasMatcher.group(1)); + BigDecimal temp = BigDecimal.ONE; + for (int i = 0; i < commas.length(); ++i) { + temp = temp.multiply(ONE_THOUSAND); + } + divider = temp; + } else { + divider = null; + } + } + + private Object scaleInput(Object obj) { + if (divider != null) { + if (obj instanceof BigDecimal) { + obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP); + } else if (obj instanceof Double) { + obj = (Double)obj / divider.doubleValue(); + } else { + throw new UnsupportedOperationException(); + } + } + return obj; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + obj = scaleInput(obj); + return df.format(obj, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + } + + private Format createNumberFormat(String formatStr, double cellValue) { + String format = cleanFormatForNumber(formatStr); + DecimalFormatSymbols symbols = decimalSymbols; + + // Do we need to change the grouping character? + // eg for a format like #'##0 which wants 12'345 not 12,345 + Matcher agm = alternateGrouping.matcher(format); + if (agm.find()) { + char grouping = agm.group(2).charAt(0); + // Only replace the grouping character if it is not the default + // grouping character for the US locale (',') in order to enable + // correct grouping for non-US locales. + if (grouping != ',') { + symbols = DecimalFormatSymbols.getInstance(locale); + + symbols.setGroupingSeparator(grouping); + String oldPart = agm.group(1); + String newPart = oldPart.replace(grouping, ','); + format = format.replace(oldPart, newPart); + } + } + + try { + return new InternalDecimalFormatWithScale(format, symbols); + } catch (IllegalArgumentException iae) { + logger.debug("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + } + + /** + * Returns a default format for a cell. + * + * @param cell The cell + * @return a default format + */ + public Format getDefaultFormat(Cell cell) { + return getDefaultFormat(cell.getNumericCellValue()); + } + + private Format getDefaultFormat(double cellValue) { + localeChangedObservable.checkForLocaleChange(); + + // for numeric cells try user supplied default + if (defaultNumFormat != null) { + return defaultNumFormat; + + // otherwise use general format + } + return generalNumberFormat; + } + + /** + * Performs Excel-style date formatting, using the supplied Date and format + */ + private String performDateFormatting(Date d, Format dateFormat) { + return (dateFormat != null ? dateFormat : defaultDateformat).format(d); + } + + /** + * Returns the formatted value of an Excel date as a String based on the cell's DataFormat. + * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc. + *

+ * If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a + * number format, or no rules apply, the cell's style format is used. If the style does not have a format, the + * default date format is applied. + * + * @param cell to format + * @param cfEvaluator ConditionalFormattingEvaluator (if available) + * @return Formatted value + */ + private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + Format dateFormat = getFormat(cell, cfEvaluator); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); + } + Date d = cell.getDateCellValue(); + return performDateFormatting(d, dateFormat); + } + + /** + * Returns the formatted value of an Excel number as a String based on the cell's DataFormat. + * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800) + * 555-1234". + *

+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell + * style. + * + * @param cell The cell + * @param cfEvaluator if available, or null + * @return a formatted number string + */ + private String getFormattedNumberString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + + Format numberFormat = getFormat(cell, cfEvaluator); + double d = cell.getNumericCellValue(); + if (numberFormat == null) { + return String.valueOf(d); + } + String formatted = numberFormat.format(new Double(d)); + return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString) { + return formatRawCellContents(value, formatIndex, formatString, false); + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) { + localeChangedObservable.checkForLocaleChange(); + + // Is it a date? + if (DateUtil.isADateFormat(formatIndex, formatString)) { + if (DateUtil.isValidExcelDate(value)) { + Format dateFormat = getFormat(value, formatIndex, formatString); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value); + } + Date d = DateUtil.getJavaDate(value, use1904Windowing); + return performDateFormatting(d, dateFormat); + } + // RK: Invalid dates are 255 #s. + if (emulateCSV) { + return invalidDateTimeString; + } + } + + // else Number + Format numberFormat = getFormat(value, formatIndex, formatString); + if (numberFormat == null) { + return String.valueOf(value); + } + + // When formatting 'value', double to text to BigDecimal produces more + // accurate results than double to Double in JDK8 (as compared to + // previous versions). However, if the value contains E notation, this + // would expand the values, which we do not want, so revert to + // original method. + String result; + final String textValue = NumberToTextConverter.toText(value); + if (textValue.indexOf('E') > -1) { + result = numberFormat.format(new Double(value)); + } else { + result = numberFormat.format(new BigDecimal(textValue)); + } + // Complete scientific notation by adding the missing +. + if (result.indexOf('E') > -1 && !result.contains("E-")) { + result = result.replaceFirst("E", "E+"); + } + return result; + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel format + * pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formulas in formula type cells + * will not be evaluated. + *

+ * + * @param cell The cell + * @return the formatted cell value as a String + */ + public String formatCellValue(Cell cell) { + return formatCellValue(cell, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ * + * @param cell The cell (can be null) + * @param evaluator The FormulaEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator) { + return formatCellValue(cell, evaluator, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ *

+ * When a ConditionalFormattingEvaluator is present, it is checked first to see if there is a number format to + * apply. If multiple rules apply, the last one is used. If no ConditionalFormattingEvaluator is present, no rules + * apply, or the applied rules do not define a format, the cell's style format is used. + *

+ *

+ * The two evaluators should be from the same context, to avoid inconsistencies in cached values. + *

+ * + * @param cell The cell (can be null) + * @param evaluator The FormulaEvaluator (can be null) + * @param cfEvaluator ConditionalFormattingEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) { + localeChangedObservable.checkForLocaleChange(); + + if (cell == null) { + return ""; + } + + CellType cellType = cell.getCellType(); + if (cellType == CellType.FORMULA) { + if (evaluator == null) { + return cell.getCellFormula(); + } + cellType = evaluator.evaluateFormulaCell(cell); + } + switch (cellType) { + case NUMERIC: + + // if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { + return getFormattedDateString(cell, cfEvaluator); + // } + // return getFormattedNumberString(cell, cfEvaluator); + + case STRING: + return cell.getRichStringCellValue().getString(); + + case BOOLEAN: + return cell.getBooleanCellValue() ? "TRUE" : "FALSE"; + case BLANK: + return ""; + case ERROR: + return FormulaError.forInt(cell.getErrorCellValue()).getString(); + default: + throw new RuntimeException("Unexpected celltype (" + cellType + ")"); + } + } + + /** + *

+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is + * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells + * with the General format. + *

+ *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param format A Format instance to be used as a default + * @see java.text.Format#format + */ + public void setDefaultNumberFormat(Format format) { + for (Map.Entry entry : formats.entrySet()) { + if (entry.getValue() == generalNumberFormat) { + entry.setValue(format); + } + } + defaultNumFormat = format; + } + + /** + * Adds a new format to the available formats. + *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param excelFormatStr The data format string + * @param format A Format instance + */ + public void addFormat(String excelFormatStr, Format format) { + formats.put(excelFormatStr, format); + } + + // Some custom formats + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + private static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT); + DecimalFormat result = new DecimalFormat(fmt, dsf); + result.setParseIntegerOnly(true); + return result; + } + + /** + * Enables excel style rounding mode (round half up) on the Decimal Format given. + */ + public static void setExcelStyleRoundingMode(DecimalFormat format) { + setExcelStyleRoundingMode(format, RoundingMode.HALF_UP); + } + + /** + * Enables custom rounding mode on the given Decimal Format. + * + * @param format DecimalFormat + * @param roundingMode RoundingMode + */ + public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { + format.setRoundingMode(roundingMode); + } + + /** + * If the Locale has been changed via {@link LocaleUtil#setUserLocale(Locale)} the stored formats need to be + * refreshed. All formats which aren't originated from DataFormatter itself, i.e. all Formats added via + * {@link DataFormatter#addFormat(String, Format)} and {@link DataFormatter#setDefaultNumberFormat(Format)}, need to + * be added again. To notify callers, the returned {@link Observable} should be used. The Object in + * {@link Observer#update(Observable, Object)} is the new Locale. + * + * @return the listener object, where callers can register themselves + */ + public Observable getLocaleChangedObservable() { + return localeChangedObservable; + } + + /** + * Update formats when locale has been changed + * + * @param observable usually this is our own Observable instance + * @param localeObj only reacts on Locale objects + */ + public void update(Observable observable, Object localeObj) { + if (!(localeObj instanceof Locale)) {return;} + Locale newLocale = (Locale)localeObj; + if (!localeIsAdapting || newLocale.equals(locale)) {return;} + + locale = newLocale; + + dateSymbols = DateFormatSymbols.getInstance(locale); + decimalSymbols = DecimalFormatSymbols.getInstance(locale); + generalNumberFormat = new ExcelGeneralNumberFormat(locale); + + // taken from Date.toString() + defaultDateformat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", dateSymbols); + defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone()); + + // init built-in formats + + formats.clear(); + Format zipFormat = ZipPlusFourFormat.instance; + addFormat("00000\\-0000", zipFormat); + addFormat("00000-0000", zipFormat); + + Format phoneFormat = PhoneFormat.instance; + // allow for format string variations + addFormat("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("[<=9999999]###-####;(###) ###-####", phoneFormat); + addFormat("###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("###-####;(###) ###-####", phoneFormat); + + Format ssnFormat = SSNFormat.instance; + addFormat("000\\-00\\-0000", ssnFormat); + addFormat("000-00-0000", ssnFormat); + } + + /** + * Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class SSNFormat extends Format { + public static final Format instance = new SSNFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private SSNFormat() { + // enforce singleton + } + + /** + * Format a number as an SSN + */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class ZipPlusFourFormat extends Format { + public static final Format instance = new ZipPlusFourFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private ZipPlusFourFormat() { + // enforce singleton + } + + /** + * Format a number as Zip + 4 + */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class PhoneFormat extends Format { + public static final Format instance = new PhoneFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + + private PhoneFormat() { + // enforce singleton + } + + /** + * Format a number as a phone number + */ + public static String format(Number num) { + String result = df.format(num); + StringBuilder sb = new StringBuilder(); + String seg1, seg2, seg3; + int len = result.length(); + if (len <= 4) { + return result; + } + + seg3 = result.substring(len - 4, len); + seg2 = result.substring(Math.max(0, len - 7), len - 4); + seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7)); + + if (seg1.trim().length() > 0) { + sb.append('(').append(seg1).append(") "); + } + if (seg2.trim().length() > 0) { + sb.append(seg2).append('-'); + } + sb.append(seg3); + return sb.toString(); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class that does nothing and always returns a constant string. + * + * This format is used to simulate Excel's handling of a format string of all # when the value is 0. Excel will + * output "", Java will output "0". + */ + @SuppressWarnings("serial") + private static final class ConstantStringFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + private final String str; + + public ConstantStringFormat(String s) { + str = s; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(str); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Workaround until we merge {@link DataFormatter} with {@link CellFormat}. Constant, non-cachable wrapper around a + * {@link CellFormatResult} + */ + @SuppressWarnings("serial") + private final class CellFormatResultWrapper extends Format { + private final CellFormatResult result; + + private CellFormatResultWrapper(CellFormatResult result) { + this.result = result; + } + + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + if (emulateCSV) { + return toAppendTo.append(result.text); + } else { + return toAppendTo.append(result.text.trim()); + } + } + + public Object parseObject(String source, ParsePosition pos) { + return null; // Not supported + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillData2.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillData2.java new file mode 100644 index 0000000..1ac7f43 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillData2.java @@ -0,0 +1,15 @@ +package ai.chat2db.excel.test.temp.fill; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +public class FillData2 { + private String test; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillTempTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillTempTest.java new file mode 100644 index 0000000..71073be --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/fill/FillTempTest.java @@ -0,0 +1,227 @@ +package ai.chat2db.excel.test.temp.fill; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.enums.WriteDirectionEnum; +import ai.chat2db.excel.test.demo.fill.FillData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.fill.FillConfig; +import ai.chat2db.excel.write.metadata.fill.FillWrapper; + +import org.junit.jupiter.api.Test; + +/** + * 写的填充写法 + * + * @author Jiaju Zhuang + * @since 2.1.1 + */ + +public class FillTempTest { + /** + * 最简单的填充 + * + * @since 2.1.1 + */ + @Test + public void simpleFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + String templateFileName = "/Users/zhuangjiaju/Downloads/simple.xlsx"; + + // 方案1 根据对象填充 + String fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"; + // 这里 会填充到第一个sheet, 然后文件流会自动关闭 + FillData fillData = new FillData(); + fillData.setName("张三"); + fillData.setNumber(5.2); + EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData); + + //// 方案2 根据Map填充 + //fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"; + //// 这里 会填充到第一个sheet, 然后文件流会自动关闭 + //Map map = new HashMap(); + //map.put("name", "张三"); + //map.put("number", 5.2); + //EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map); + } + + /** + * 填充列表 + * + * @since 2.1.1 + */ + @Test + public void listFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // 填充list 的时候还要注意 模板中{.} 多了个点 表示list + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx"; + + // 方案1 一下子全部放到内存里面 并填充 + String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; + // 这里 会填充到第一个sheet, 然后文件流会自动关闭 + EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data()); + + // 方案2 分多次 填充 会使用文件缓存(省内存) + fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + // 千万别忘记关闭流 + excelWriter.finish(); + } + + /** + * 复杂的填充 + * + * @since 2.1.1 + */ + @Test + public void complexFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 + // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 + // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存 + // 如果数据量大 list不是最后一行 参照下一个 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet); + excelWriter.finish(); + } + + /** + * 数据量大的复杂填充 + *

+ * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 + */ + @Test + public void complexFillWithTable() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + // 这里模板 删除了list以后的数据,也就是统计的这一行 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complexFillWithTable.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 直接写入数据 + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + + // 写入list之前的数据 + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // list 后面还有个统计 想办法手动写入 + // 这里偷懒直接用list 也可以用对象 + List> totalListList = new ArrayList>(); + List totalList = new ArrayList(); + totalListList.add(totalList); + totalList.add(null); + totalList.add(null); + totalList.add(null); + // 第四列 + totalList.add("统计:1000"); + // 这里是write 别和fill 搞错了 + excelWriter.write(totalListList, writeSheet); + excelWriter.finish(); + // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 + // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 + } + + /** + * 横向的填充 + * + * @since 2.1.1 + */ + @Test + public void horizontalFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx"; + + String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // 别忘记关闭流 + excelWriter.finish(); + } + + /** + * 多列表组合填充填充 + * + * @since 2.2.0-beta1 + */ + @Test + public void compositeFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list + String templateFileName = + TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "composite.xlsx"; + + String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); + // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹 + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data2", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + excelWriter.fill(new FillWrapper("data3", data()), writeSheet); + + Map map = new HashMap(); + //map.put("date", "2019年10月9日13:28:28"); + map.put("date", new Date()); + + excelWriter.fill(map, writeSheet); + + // 别忘记关闭流 + excelWriter.finish(); + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Data1662.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Data1662.java new file mode 100644 index 0000000..18b17b9 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Data1662.java @@ -0,0 +1,25 @@ +package ai.chat2db.excel.test.temp.issue1662; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor +public class Data1662 { + @ExcelProperty(index = 0) + private String str; + @ExcelProperty(index = 1) + private Date date; + @ExcelProperty(index = 2) + private double r; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Issue1662Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Issue1662Test.java new file mode 100644 index 0000000..63cd6fd --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1662/Issue1662Test.java @@ -0,0 +1,41 @@ +package ai.chat2db.excel.test.temp.issue1662; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.util.TestFileUtil; + +import org.junit.jupiter.api.Test; + +public class Issue1662Test { + @Test + public void test1662() { + String fileName = TestFileUtil.getPath() + "Test1939" + ".xlsx"; + System.out.println(fileName); + EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = new ArrayList(); + List head1 = new ArrayList(); + head0.add("xx"); + head0.add("日期"); + list.add(head0); + head1.add("日期"); + list.add(head1); + return list; + } + + private List> dataList() { + List> list = new ArrayList>(); + List data = new ArrayList(); + data.add("字符串"); + data.add(new Date()); + data.add(0.56); + list.add(data); + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillData.java new file mode 100644 index 0000000..cd19632 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillData.java @@ -0,0 +1,13 @@ +package ai.chat2db.excel.test.temp.issue1663; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class FillData { + private String name; + private double number; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillTest.java new file mode 100644 index 0000000..5c90f5b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue1663/FillTest.java @@ -0,0 +1,50 @@ +package ai.chat2db.excel.test.temp.issue1663; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.enums.WriteDirectionEnum; +import ai.chat2db.excel.test.demo.fill.FillData; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.fill.FillConfig; +import ai.chat2db.excel.write.metadata.fill.FillWrapper; + +import org.junit.jupiter.api.Test; + +public class FillTest { + @Test + public void TestFillNullPoint() { + String templateFileName = + TestFileUtil.getPath() + "temp/issue1663" + File.separator + "template.xlsx"; + + String fileName = TestFileUtil.getPath() + "temp/issue1663" + File.separator + "issue1663.xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.VERTICAL).build(); + excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); + + Map map = new HashMap(); + // Variable {date} does not exist in the template.xlsx, which should be ignored instead of reporting an error. + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + excelWriter.finish(); + } + + private List data() { + List list + = new ArrayList(); + for (int i = 0; i < 10; i++) { + ai.chat2db.excel.test.demo.fill.FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443.java new file mode 100644 index 0000000..ddb804a --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443.java @@ -0,0 +1,13 @@ +package ai.chat2db.excel.test.temp.issue2443; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class Issue2443 { + private int a; + private int b; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443Test.java new file mode 100644 index 0000000..ca2223f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/issue2443/Issue2443Test.java @@ -0,0 +1,58 @@ +package ai.chat2db.excel.test.temp.issue2443; + +import java.io.File; +import java.text.ParseException; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.property.ExcelContentProperty; +import ai.chat2db.excel.read.listener.PageReadListener; +import ai.chat2db.excel.util.NumberUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +@Slf4j +public class Issue2443Test { + //CS304 (manually written) Issue link: https://github.com/alibaba/easyexcel/issues/2443 + @Test + public void IssueTest1() { + String fileName = TestFileUtil.getPath() + "temp/issue2443" + File.separator + "date1.xlsx"; + EasyExcel.read(fileName, Issue2443.class, new PageReadListener(dataList -> { + for (Issue2443 issueData : dataList) { + log.info("读取到一条数据{}", JSON.toJSONString(issueData)); + } + })).sheet().doRead(); + } + + //CS304 (manually written) Issue link: https://github.com/alibaba/easyexcel/issues/2443 + @Test + public void IssueTest2() { + String fileName = TestFileUtil.getPath() + "temp/issue2443" + File.separator + "date2.xlsx"; + EasyExcel.read(fileName, Issue2443.class, new PageReadListener(dataList -> { + for (Issue2443 issueData : dataList) { + log.info("读取到一条数据{}", JSON.toJSONString(issueData)); + } + })).sheet().doRead(); + } + + @Test + public void parseIntegerTest1() throws ParseException { + String string = "1.00"; + ExcelContentProperty contentProperty = null; + int Int = NumberUtils.parseInteger(string, contentProperty); + Assertions.assertEquals(1, Int); + } + + @Test + public void parseIntegerTest2() throws ParseException { + String string = "2.00"; + ExcelContentProperty contentProperty = null; + int Int = NumberUtils.parseInteger(string, contentProperty); + Assertions.assertEquals(2, Int); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeData.java new file mode 100644 index 0000000..6ee6d2f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeData.java @@ -0,0 +1,66 @@ +package ai.chat2db.excel.test.temp.large; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author Jiaju Zhuang + */ +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class LargeData { + + private String str1; + + private String str2; + + private String str3; + + private String str4; + + private String str5; + + private String str6; + + private String str7; + + private String str8; + + private String str9; + + private String str10; + + private String str11; + + private String str12; + + private String str13; + + private String str14; + + private String str15; + + private String str16; + + private String str17; + + private String str18; + + private String str19; + + private String str20; + + private String str21; + + private String str22; + + private String str23; + + private String str24; + + private String str25; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeDataListener.java new file mode 100644 index 0000000..722e079 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/LargeDataListener.java @@ -0,0 +1,32 @@ +package ai.chat2db.excel.test.temp.large; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class LargeDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(LargeDataListener.class); + private int count = 0; + + @Override + public void invoke(LargeData data, AnalysisContext context) { + if (count == 0) { + LOGGER.info("First row:{}", JSON.toJSONString(data)); + } + count++; + if (count % 100000 == 0) { + LOGGER.info("Already read:{}", count); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("Large row count:{}", count); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/NoModelLargeDataListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/NoModelLargeDataListener.java new file mode 100644 index 0000000..862e81c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/NoModelLargeDataListener.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.temp.large; + +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ +public class NoModelLargeDataListener extends AnalysisEventListener> { + + private static final Logger LOGGER = LoggerFactory.getLogger(NoModelLargeDataListener.class); + private int count = 0; + + @Override + public void invoke(Map data, AnalysisContext context) { + if (count == 0) { + LOGGER.info("First row:{}", JSON.toJSONString(data)); + } + count++; + if (count % 100000 == 0) { + LOGGER.info("Already read:{}", count); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("Large row count:{}", count); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/TempLargeDataTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/TempLargeDataTest.java new file mode 100644 index 0000000..d583561 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/large/TempLargeDataTest.java @@ -0,0 +1,222 @@ +package ai.chat2db.excel.test.temp.large; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; + +import org.apache.poi.openxml4j.util.ZipSecureFile; +import org.apache.poi.xssf.streaming.SXSSFCell; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Jiaju Zhuang + */ + +public class TempLargeDataTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(TempLargeDataTest.class); + private int i = 0; + + private static File fileFill07; + private static File template07; + private static File fileCsv; + private static File fileWrite07; + private static File fileWriteTemp07; + private static File fileWritePoi07; + + @BeforeAll + public static void init() { + fileFill07 = TestFileUtil.createNewFile("largefill07.xlsx"); + fileWrite07 = TestFileUtil.createNewFile("large" + File.separator + "fileWrite07.xlsx"); + fileWriteTemp07 = TestFileUtil.createNewFile("large" + File.separator + "fileWriteTemp07.xlsx"); + fileWritePoi07 = TestFileUtil.createNewFile("large" + File.separator + "fileWritePoi07.xlsx"); + template07 = TestFileUtil.readFile("large" + File.separator + "fill.xlsx"); + fileCsv = TestFileUtil.createNewFile("largefileCsv.csv"); + } + + @Test + public void read() throws Exception { + long start = System.currentTimeMillis(); + EasyExcel.read(new FileInputStream("D:\\test\\MRP生产视图(1).xlsx"), LargeData.class, new LargeDataListener()) + .headRowNumber(2).sheet().doRead(); + LOGGER.info("Large data total time spent:{}", System.currentTimeMillis() - start); + } + + @Test + public void noModelRead() throws Exception { + ZipSecureFile.setMaxEntrySize(Long.MAX_VALUE); + long start = System.currentTimeMillis(); + EasyExcel.read(TestFileUtil.readUserHomeFile("test/ld.xlsx"), new NoModelLargeDataListener()) + .sheet().doRead(); + LOGGER.info("Large data total time spent:{}", System.currentTimeMillis() - start); + } + + @Test + public void noModelRead2() throws Exception { + Field field = ZipSecureFile.class.getDeclaredField("MAX_ENTRY_SIZE"); + field.setAccessible(true); + field.set(null, Long.MAX_VALUE); + + long start = System.currentTimeMillis(); + EasyExcel.read( + new File("/Users/zhuangjiaju/IdeaProjects/easyexcel/target/test-classes/large1617887262709.xlsx"), + new NoModelLargeDataListener()) + .sheet().doRead(); + LOGGER.info("Large data total time spent:{}", System.currentTimeMillis() - start); + } + + @Test + public void t04Write() throws Exception { + ExcelWriter excelWriter = EasyExcel.write(fileWriteTemp07, + ai.chat2db.excel.test.core.large.LargeData.class).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 2; j++) { + excelWriter.write(data(), writeSheet); + } + excelWriter.finish(); + + long start = System.currentTimeMillis(); + excelWriter = EasyExcel.write(fileWrite07, ai.chat2db.excel.test.core.large.LargeData.class).build(); + writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 5000; j++) { + excelWriter.write(data(), writeSheet); + LOGGER.info("{} write success.", j); + } + excelWriter.finish(); + long cost = System.currentTimeMillis() - start; + LOGGER.info("write cost:{}", cost); + start = System.currentTimeMillis(); + try (FileOutputStream fileOutputStream = new FileOutputStream(fileWritePoi07)) { + SXSSFWorkbook workbook = new SXSSFWorkbook(); + SXSSFSheet sheet = workbook.createSheet("sheet1"); + for (int i = 0; i < 100 * 5000; i++) { + SXSSFRow row = sheet.createRow(i); + for (int j = 0; j < 25; j++) { + SXSSFCell cell = row.createCell(j); + cell.setCellValue("str-" + j + "-" + i); + } + if (i % 5000 == 0) { + LOGGER.info("{} write success.", i); + } + } + workbook.write(fileOutputStream); + workbook.dispose(); + workbook.close(); + } + long costPoi = System.currentTimeMillis() - start; + LOGGER.info("poi write cost:{}", System.currentTimeMillis() - start); + LOGGER.info("{} vs {}", cost, costPoi); + Assertions.assertTrue(costPoi * 2 > cost); + } + + @Test + public void t04WriteExcel() throws Exception { + IntStream.rangeClosed(0, 100).forEach(index -> { + ExcelWriter excelWriter = EasyExcel.write(fileWriteTemp07, + ai.chat2db.excel.test.core.large.LargeData.class).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 5000; j++) { + excelWriter.write(data(), writeSheet); + } + excelWriter.finish(); + LOGGER.info("{} 完成", index); + }); + } + + @Test + public void t04WriteExcelNo() throws Exception { + IntStream.rangeClosed(0, 10000).forEach(index -> { + ExcelWriter excelWriter = EasyExcel.write(fileWriteTemp07, + ai.chat2db.excel.test.core.large.LargeData.class).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + for (int j = 0; j < 50; j++) { + excelWriter.write(data(), writeSheet); + } + excelWriter.finish(); + LOGGER.info("{} 完成", index); + }); + } + + @Test + public void t04WriteExcelPoi() throws Exception { + IntStream.rangeClosed(0, 10000).forEach(index -> { + try (FileOutputStream fileOutputStream = new FileOutputStream(fileWritePoi07)) { + SXSSFWorkbook workbook = new SXSSFWorkbook(500); + //workbook.setCompressTempFiles(true); + SXSSFSheet sheet = workbook.createSheet("sheet1"); + for (int i = 0; i < 100 * 50; i++) { + SXSSFRow row = sheet.createRow(i); + for (int j = 0; j < 25; j++) { + String str = "str-" + j + "-" + i; + //if (i + 10000 == j) { + SXSSFCell cell = row.createCell(j); + cell.setCellValue(str); + //System.out.println(str); + //} + } + if (i % 5000 == 0) { + LOGGER.info("{} write success.", i); + } + } + workbook.write(fileOutputStream); + workbook.dispose(); + workbook.close(); + } catch (Exception e) { + e.printStackTrace(); + } + LOGGER.info("{} 完成", index); + }); + } + + private List data() { + List list = new ArrayList<>(); + + int size = i + 100; + for (; i < size; i++) { + LargeData largeData = new LargeData(); + list.add(largeData); + largeData.setStr1("str1-" + i); + largeData.setStr2("str2-" + i); + largeData.setStr3("str3-" + i); + largeData.setStr4("str4-" + i); + largeData.setStr5("str5-" + i); + largeData.setStr6("str6-" + i); + largeData.setStr7("str7-" + i); + largeData.setStr8("str8-" + i); + largeData.setStr9("str9-" + i); + largeData.setStr10("str10-" + i); + largeData.setStr11("str11-" + i); + largeData.setStr12("str12-" + i); + largeData.setStr13("str13-" + i); + largeData.setStr14("str14-" + i); + largeData.setStr15("str15-" + i); + largeData.setStr16("str16-" + i); + largeData.setStr17("str17-" + i); + largeData.setStr18("str18-" + i); + largeData.setStr19("str19-" + i); + largeData.setStr20("str20-" + i); + largeData.setStr21("str21-" + i); + largeData.setStr22("str22-" + i); + largeData.setStr23("str23-" + i); + largeData.setStr24("str24-" + i); + largeData.setStr25("str25-" + i); + } + return list; + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi2Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi2Test.java new file mode 100644 index 0000000..c80786e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi2Test.java @@ -0,0 +1,46 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.IOException; + +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class Poi2Test { + private static final Logger LOGGER = LoggerFactory.getLogger(Poi2Test.class); + + @Test + public void test() throws IOException { + String file = "D:\\test\\珠海.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + } + + @Test + public void lastRowNumXSSF() throws IOException { + String file = "D:\\test\\珠海.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi3Test.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi3Test.java new file mode 100644 index 0000000..c9f130e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/Poi3Test.java @@ -0,0 +1,59 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; + +import ai.chat2db.excel.test.util.TestFileUtil; + +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; +import org.apache.poi.poifs.crypt.Encryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class Poi3Test { + private static final Logger LOGGER = LoggerFactory.getLogger(Poi3Test.class); + + @Test + public void Encryption() throws Exception { + String file = TestFileUtil.getPath() + "large" + File.separator + "large07.xlsx"; + POIFSFileSystem fs = new POIFSFileSystem(); + EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); + Encryptor enc = info.getEncryptor(); + enc.confirmPassword("foobaa"); + OPCPackage opc = OPCPackage.open(new File(file), PackageAccess.READ_WRITE); + OutputStream os = enc.getDataStream(fs); + opc.save(os); + opc.close(); + + // Write out the encrypted version + FileOutputStream fos = new FileOutputStream("D:\\test\\99999999999.xlsx"); + fs.writeFilesystem(fos); + fos.close(); + fs.close(); + + } + + @Test + public void Encryption2() throws Exception { + Biff8EncryptionKey.setCurrentUserPassword("123456"); + POIFSFileSystem fs = new POIFSFileSystem(new File("d:/test/simple03.xls"), true); + HSSFWorkbook hwb = new HSSFWorkbook(fs.getRoot(), true); + Biff8EncryptionKey.setCurrentUserPassword(null); + System.out.println(hwb.getSheetAt(0).getSheetName()); + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiDateFormatTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiDateFormatTest.java new file mode 100644 index 0000000..1d5c41c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiDateFormatTest.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.IOException; + +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class PoiDateFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiDateFormatTest.class); + + @Test + public void read() throws IOException { + String file + = "/Users/zhuangjiaju/IdeaProjects/easyexcel/easyexcel-test/src/test/resources/dataformat/dataformat.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook( file); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(7); + XSSFCell cell = row.getCell(0); + LOGGER.info("dd{}", cell.getDateCellValue()); + LOGGER.info("dd{}", cell.getNumericCellValue()); + + LOGGER.info("dd{}", DateUtil.isCellDateFormatted(cell)); + + + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiEncryptTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiEncryptTest.java new file mode 100644 index 0000000..e2380bb --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiEncryptTest.java @@ -0,0 +1,75 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.core.encrypt.EncryptData; +import ai.chat2db.excel.test.core.simple.SimpleData; +import ai.chat2db.excel.test.util.TestFileUtil; + +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; +import org.apache.poi.poifs.crypt.Encryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; + +/** + * TODO + * + * @author Jiaju Zhuang + */ + +public class PoiEncryptTest { + @Test + public void encrypt() throws Exception { + + XSSFWorkbook workbook = new XSSFWorkbook(); + SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(workbook); + + Sheet sheet = sxssfWorkbook.createSheet("sheet1"); + sheet.createRow(0).createCell(0).setCellValue("T2"); + + POIFSFileSystem fs = new POIFSFileSystem(); + EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); + + Encryptor enc = info.getEncryptor(); + enc.confirmPassword("123456"); + + // write the workbook into the encrypted OutputStream + OutputStream encos = enc.getDataStream(fs); + sxssfWorkbook.write(encos); + sxssfWorkbook.dispose(); + sxssfWorkbook.close(); + encos.close(); // this is necessary before writing out the FileSystem + + OutputStream os = new FileOutputStream( + TestFileUtil.createNewFile("encrypt" + System.currentTimeMillis() + ".xlsx")); + fs.writeFilesystem(os); + os.close(); + fs.close(); + } + + @Test + public void encryptExcel() throws Exception { + EasyExcel.write(TestFileUtil.createNewFile("encryptv2" + System.currentTimeMillis() + ".xlsx"), + EncryptData.class).password("123456") + .sheet().doWrite(data()); + } + + private List data() { + List list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + SimpleData simpleData = new SimpleData(); + simpleData.setName("姓名" + i); + list.add(simpleData); + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiFormatTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiFormatTest.java new file mode 100644 index 0000000..938d5ff --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiFormatTest.java @@ -0,0 +1,52 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.IOException; +import java.util.Locale; + +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class PoiFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiFormatTest.class); + + @Test + public void lastRowNum() throws IOException { + String file = "D:\\test\\原文件.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + } + + @Test + public void lastRowNumXSSF() throws IOException { + String file = "/Users/zhuangjiaju/Downloads/测试格式.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(1); + XSSFCell xssfCell = row.getCell(0); + DataFormatter d = new DataFormatter(Locale.CHINA); + LOGGER.info("fo:{}", d.formatCellValue(xssfCell)); + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiTest.java new file mode 100644 index 0000000..5c0719e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiTest.java @@ -0,0 +1,316 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; + +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.test.util.TestFileUtil; + +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFFont; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class PoiTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiTest.class); + + @Test + public void lastRowNum() throws IOException { + String file = "/Users/zhuangjiaju/test/test3.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("dd{}", row.getCell(0).getColumnIndex()); + Date date = row.getCell(1).getDateCellValue(); + + } + + @Test + public void lastRowNumXSSF() throws IOException { + + String file = "/Users/zhuangjiaju/test/test3 copy 10.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream(file)); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(1); + LOGGER.info("dd{}", row.getCell(0).getRow().getRowNum()); + LOGGER.info("dd{}", xssfSheet.getLastRowNum()); + + XSSFCellStyle cellStyle = row.getCell(0).getCellStyle(); + LOGGER.info("size1:{}", cellStyle.getFontIndexAsInt()); + + XSSFCellStyle cellStyle1 = xssfWorkbook.createCellStyle(); + LOGGER.info("size2:{}", cellStyle1.getFontIndexAsInt()); + + cellStyle1.cloneStyleFrom(cellStyle); + LOGGER.info("size3:{}", cellStyle1.getFontIndexAsInt()); + + LOGGER.info("bbb:{}", cellStyle1.getFont().getXSSFColor().getIndex()); + LOGGER.info("bbb:{}", cellStyle1.getFont().getXSSFColor().getIndexed()); + XSSFColor myColor = new XSSFColor(cellStyle1.getFont().getXSSFColor().getRGB(), null); + LOGGER.info("bbb:{}", cellStyle1.getFont().getXSSFColor().getRGB()); + LOGGER.info("bbb:{}", cellStyle1.getFont().getXSSFColor().getARGBHex()); + + LOGGER.info("bbb:{}", cellStyle1.getFont().getBold()); + LOGGER.info("bbb:{}", cellStyle1.getFont().getFontName()); + + XSSFFont xssfFont = xssfWorkbook.createFont(); + + xssfFont.setColor(myColor); + + xssfFont.setFontHeightInPoints((short)50); + xssfFont.setBold(Boolean.TRUE); + cellStyle1.setFont(xssfFont); + cellStyle1.setFillForegroundColor(IndexedColors.PINK.getIndex()); + + LOGGER.info("aaa:{}", cellStyle1.getFont().getColor()); + + row.getCell(1).setCellStyle(cellStyle1); + row.getCell(1).setCellValue(3334l); + + XSSFCellStyle cellStyle2 = xssfWorkbook.createCellStyle(); + cellStyle2.cloneStyleFrom(cellStyle); + cellStyle2.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + //cellStyle2.setFont(cellStyle1.getFont()); + row.getCell(2).setCellStyle(cellStyle2); + row.getCell(2).setCellValue(3334l); + //LOGGER.info("date1:{}", row.getCell(0).getStringCellValue()); + //LOGGER.info("date2:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).getIndex()); + //LOGGER.info("date2:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).isRGB()); + //LOGGER.info("date4:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).isIndexed()); + //LOGGER.info("date3:{}", cellStyle.getFont().getXSSFColor().getRGB()); + //LOGGER.info("date4:{}", cellStyle.getFont().getCTFont().getColorArray(0).getRgb()); + FileOutputStream fileOutputStream = new FileOutputStream(file); + xssfWorkbook.write(fileOutputStream); + fileOutputStream.flush(); + xssfWorkbook.close(); + } + + @Test + public void lastRowNumXSSFv22() throws IOException { + + String file = "/Users/zhuangjiaju/test/test3 copy 2.xls"; + HSSFWorkbook xssfWorkbook = new HSSFWorkbook(new FileInputStream(file)); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + HSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + HSSFRow row = xssfSheet.getRow(1); + LOGGER.info("dd{}", row.getCell(0).getRow().getRowNum()); + LOGGER.info("dd{}", xssfSheet.getLastRowNum()); + + HSSFCellStyle cellStyle = row.getCell(0).getCellStyle(); + LOGGER.info("单元格1的字体:{}", cellStyle.getFontIndexAsInt()); + + HSSFCellStyle cellStyle1 = xssfWorkbook.createCellStyle(); + LOGGER.info("size2:{}", cellStyle1.getFontIndexAsInt()); + + cellStyle1.cloneStyleFrom(cellStyle); + LOGGER.info("单元格2的字体:{}", cellStyle1.getFontIndexAsInt()); + + LOGGER.info("bbb:{}", cellStyle1.getFont(xssfWorkbook).getColor()); + + HSSFFont xssfFont = xssfWorkbook.createFont(); + + xssfFont.setColor(cellStyle1.getFont(xssfWorkbook).getColor()); + xssfFont.setFontHeightInPoints((short)50); + xssfFont.setBold(Boolean.TRUE); + cellStyle1.setFont(xssfFont); + cellStyle1.setFillForegroundColor(IndexedColors.PINK.getIndex()); + + LOGGER.info("aaa:{}", cellStyle1.getFont(xssfWorkbook).getColor()); + + row.getCell(1).setCellStyle(cellStyle1); + row.getCell(1).setCellValue(3334l); + + HSSFCellStyle cellStyle2 = xssfWorkbook.createCellStyle(); + cellStyle2.cloneStyleFrom(cellStyle); + cellStyle2.setFillForegroundColor(IndexedColors.BLUE.getIndex()); + //cellStyle2.setFont(cellStyle1.getFont()); + row.getCell(2).setCellStyle(cellStyle2); + row.getCell(2).setCellValue(3334l); + //LOGGER.info("date1:{}", row.getCell(0).getStringCellValue()); + //LOGGER.info("date2:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).getIndex()); + //LOGGER.info("date2:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).isRGB()); + //LOGGER.info("date4:{}", ((XSSFColor) cellStyle.getFillForegroundColorColor()).isIndexed()); + //LOGGER.info("date3:{}", cellStyle.getFont().getXSSFColor().getRGB()); + //LOGGER.info("date4:{}", cellStyle.getFont().getCTFont().getColorArray(0).getRgb()); + FileOutputStream fileOutputStream = new FileOutputStream(file); + xssfWorkbook.write(fileOutputStream); + fileOutputStream.flush(); + xssfWorkbook.close(); + } + + @Test + public void lastRowNum233() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + Workbook xx = new XSSFWorkbook(file); + System.out.println(new File(file).exists()); + + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(); + Sheet xssfSheet = xssfWorkbook.getXSSFWorkbook().getSheetAt(0); + + Cell cell = xssfSheet.getRow(0).createCell(9); + cell.setCellValue("testssdf是士大夫否t"); + + FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); + xssfWorkbook.write(fileout); + xssfWorkbook.close(); + } + + @Test + public void lastRowNum255() throws IOException, InvalidFormatException { + String file = "D:\\test\\complex.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); + SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + xssfSheet.shiftRows(1, 4, 10, true, true); + + FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); + sxssfWorkbook.write(fileout); + sxssfWorkbook.dispose(); + sxssfWorkbook.close(); + + xssfWorkbook.close(); + } + + @Test + public void cp() throws IOException, InvalidFormatException { + String file = "d://test/tt.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + } + + @Test + public void lastRowNum233443() throws IOException, InvalidFormatException { + String file = "d://test/em0.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + System.out.println(xssfSheet.getLastRowNum()); + System.out.println(xssfSheet.getRow(0)); + + } + + @Test + public void lastRowNum2333() throws IOException, InvalidFormatException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); + SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + Cell cell = xssfSheet.getRow(0).createCell(9); + cell.setCellValue("testssdf是士大夫否t"); + + FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); + sxssfWorkbook.write(fileout); + sxssfWorkbook.dispose(); + sxssfWorkbook.close(); + + xssfWorkbook.close(); + } + + @Test + public void testread() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + Sheet xssfSheet = xssfWorkbook.getXSSFWorkbook().getSheetAt(0); + // + // Cell cell = xssfSheet.getRow(0).createCell(9); + + String file1 = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + + SXSSFWorkbook xssfWorkbook1 = new SXSSFWorkbook(new XSSFWorkbook(file1)); + Sheet xssfSheet1 = xssfWorkbook1.getXSSFWorkbook().getSheetAt(0); + + // Cell cell1 = xssfSheet1.getRow(0).createCell(9); + + xssfWorkbook.close(); + xssfWorkbook1.close(); + } + + @Test + public void testreadRead() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + FileUtils.readFileToByteArray(new File(file)); + } + + @Test + public void lastRowNum2332222() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + Sheet xssfSheet = xssfWorkbook.getXSSFWorkbook().getSheetAt(0); + + Cell cell = xssfSheet.getRow(0).createCell(9); + cell.setCellValue("testssdf是士大夫否t"); + + FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); + xssfWorkbook.write(fileout); + } + + @Test + public void lastRowNum23443() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + + FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); + xssfWorkbook.write(fileout); + xssfWorkbook.close(); + } + + @Test + public void lastRowNum2() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + Sheet xssfSheet = xssfWorkbook.getXSSFWorkbook().getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getPhysicalNumberOfRows()); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + LOGGER.info("一共行数:{}", xssfSheet.getFirstRowNum()); + + } + + @Test + public void lastRowNumXSSF2() throws IOException { + String file = TestFileUtil.getPath() + "fill" + File.separator + "simple.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiWriteTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiWriteTest.java new file mode 100644 index 0000000..731e722 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/PoiWriteTest.java @@ -0,0 +1,120 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.io.BufferedInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.URL; +import java.util.regex.Pattern; + +import com.alibaba.fastjson2.JSON; + +import org.apache.poi.xssf.streaming.SXSSFCell; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class PoiWriteTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiWriteTest.class); + + @Test + public void write0() throws IOException { + FileOutputStream fileOutputStream = + new FileOutputStream("D://test//tt132" + System.currentTimeMillis() + ".xlsx"); + SXSSFWorkbook sxxsFWorkbook = new SXSSFWorkbook(); + SXSSFSheet sheet = sxxsFWorkbook.createSheet("t1"); + SXSSFRow row = sheet.createRow(0); + SXSSFCell cell1 = row.createCell(0); + cell1.setCellValue(999999999999999L); + SXSSFCell cell2 = row.createCell(1); + cell2.setCellValue(1000000000000001L); + SXSSFCell cell32 = row.createCell(2); + cell32.setCellValue(300.35f); + sxxsFWorkbook.write(fileOutputStream); + } + + @Test + public void write01() throws IOException { + float ff = 300.35f; + BigDecimal bd = new BigDecimal(Float.toString(ff)); + System.out.println(bd.doubleValue()); + System.out.println(bd.floatValue()); + + } + + @Test + public void write() throws IOException { + FileOutputStream fileOutputStream = + new FileOutputStream("D://test//tt132" + System.currentTimeMillis() + ".xlsx"); + SXSSFWorkbook sxxsFWorkbook = new SXSSFWorkbook(); + SXSSFSheet sheet = sxxsFWorkbook.createSheet("t1"); + SXSSFRow row = sheet.createRow(0); + SXSSFCell cell1 = row.createCell(0); + cell1.setCellValue(Long.toString(999999999999999L)); + SXSSFCell cell2 = row.createCell(1); + cell2.setCellValue(Long.toString(1000000000000001L)); + sxxsFWorkbook.write(fileOutputStream); + } + + @Test + public void write1() throws IOException { + System.out.println(JSON.toJSONString(long2Bytes(-999999999999999L))); + System.out.println(JSON.toJSONString(long2Bytes(-9999999999999999L))); + } + + public static byte[] long2Bytes(long num) { + byte[] byteNum = new byte[8]; + for (int ix = 0; ix < 8; ++ix) { + int offset = 64 - (ix + 1) * 8; + byteNum[ix] = (byte)((num >> offset) & 0xff); + } + return byteNum; + } + + private static final Pattern FILL_PATTERN = Pattern.compile("^.*?\\$\\{[^}]+}.*?$"); + + @Test + public void part() throws IOException { + LOGGER.info("test:{}", FILL_PATTERN.matcher("${name今年${number}岁了").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("${name}今年${number}岁了").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("${name}").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("${number}").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("${name}今年").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("今年${number}岁了").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("今年${number岁了").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("${}").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("胜多负少").matches()); + } + + private static final Pattern FILL_PATTERN2 = Pattern.compile("测试"); + + @Test + public void part2() throws IOException { + LOGGER.info("test:{}", FILL_PATTERN.matcher("我是测试呀").find()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("测试u").matches()); + LOGGER.info("test:{}", FILL_PATTERN.matcher("我是测试").matches()); + + } + + @Test + public void part4() throws IOException { + //URL url=new URL("http://120.55.161.4/group1/M00/00/00/i8QJ8WFfwMiAXKYrAAACqC1MFiY641.png"); + URL url = new URL( + "https://img-blog.csdn.net/20160729002743309?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T" + + "/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"); + + InputStream in = new BufferedInputStream(url.openStream()); + + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/TestCell.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/TestCell.java new file mode 100644 index 0000000..a9eda81 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/poi/TestCell.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.temp.poi; + +import java.util.List; + +import ai.chat2db.excel.metadata.data.CellData; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * TODO + * + * @author 罗成 + **/ +@Getter +@Setter +@EqualsAndHashCode +public class TestCell { + private CellData c1; + private CellData> c2; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/CommentTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/CommentTest.java new file mode 100644 index 0000000..57b51a2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/CommentTest.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.temp.read; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.metadata.data.CellData; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class CommentTest { + private static final Logger LOGGER = LoggerFactory.getLogger(CommentTest.class); + + @Test + public void comment() throws Exception { + File file = new File("D:\\test\\d1.xlsx"); + List> datas = EasyExcel.read(file).doReadAllSync(); + for (Map data : datas) { + LOGGER.info("数据:{}", JSON.toJSONString(data)); + } + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HDListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HDListener.java new file mode 100644 index 0000000..d86fd9f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HDListener.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.test.temp.read; + +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class HDListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(HDListener.class); + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("HEAD:{}", JSON.toJSONString(headMap)); + LOGGER.info("total:{}", context.readSheetHolder().getTotal()); + + } + + @Override + public void invoke(HeadReadData data, AnalysisContext context) { + LOGGER.info("index:{}", context.readRowHolder().getRowIndex()); + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("所有数据解析完成!"); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadListener.java new file mode 100644 index 0000000..c2a6d0c --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadListener.java @@ -0,0 +1,42 @@ +package ai.chat2db.excel.test.temp.read; + +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class HeadListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(HeadListener.class); + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("HEAD:{}", JSON.toJSONString(headMap)); + LOGGER.info("total:{}", context.readSheetHolder().getTotal()); + + } + + @Override + public void invoke(HeadReadData data, AnalysisContext context) { + LOGGER.info("index:{}", context.readRowHolder().getRowIndex()); + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("所有数据解析完成!"); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadData.java new file mode 100644 index 0000000..48b8a91 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadData.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.temp.read; + +import ai.chat2db.excel.annotation.ExcelProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class HeadReadData { + @ExcelProperty({"主标题", "数据1"}) + private String h1; + @ExcelProperty({"主标题", "数据2"}) + private String h2; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadTest.java new file mode 100644 index 0000000..58c343f --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/HeadReadTest.java @@ -0,0 +1,40 @@ +package ai.chat2db.excel.test.temp.read; + +import java.io.File; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.cache.Ehcache; +import ai.chat2db.excel.test.util.TestFileUtil; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ + +public class HeadReadTest { + private static final Logger LOGGER = LoggerFactory.getLogger(HeadReadTest.class); + + @Test + public void test() throws Exception { + File file = TestFileUtil.readUserHomeFile("test/t2.xlsx"); + EasyExcel.read(file, HeadReadData.class, new HeadListener()).ignoreEmptyRow(false).sheet(0).doRead(); + + } + + @Test + public void testCache() throws Exception { + File file = new File("D:\\test\\headt1.xls"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + + LOGGER.info("------------------"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + LOGGER.info("------------------"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/TestListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/TestListener.java new file mode 100644 index 0000000..5d506d5 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/read/TestListener.java @@ -0,0 +1,27 @@ +package ai.chat2db.excel.test.temp.read; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * TODO + * + * @author JiaJu Zhuang + * @date 2020/4/9 16:33 + **/ +@Slf4j +public class TestListener extends AnalysisEventListener { + + @Override + public void invoke(Object o, AnalysisContext analysisContext) { + log.info("解析一条:{}", JSON.toJSONString(o)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData1.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData1.java new file mode 100644 index 0000000..6d67d3d --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData1.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class DemoData1 { + @ExcelProperty("字符串标题") + @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 42) + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; + /** + * 忽略这个字段 + */ + @ExcelIgnore + private String ignore; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData2.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData2.java new file mode 100644 index 0000000..48b9859 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/DemoData2.java @@ -0,0 +1,30 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.Date; + +import ai.chat2db.excel.annotation.ExcelIgnore; +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.enums.poi.FillPatternTypeEnum; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode +public class DemoData2 { + @ExcelProperty("字符串标题") + @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 42) + private String string; + @ExcelProperty("日期标题") + private Date date; + @ExcelProperty("数字标题") + private Double doubleData; + /** + * 忽略这个字段 + */ + @ExcelIgnore + private String ignore; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgListener.java new file mode 100644 index 0000000..0aa619e --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgListener.java @@ -0,0 +1,35 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.Map; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class HgListener extends AnalysisEventListener> { + private static final Logger LOGGER = LoggerFactory.getLogger(HgListener.class); + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + @Override + public void invoke(Map data, AnalysisContext context) { + LOGGER.info("index:{}", context.readRowHolder().getRowIndex()); + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("所有数据解析完成!"); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgTest.java new file mode 100644 index 0000000..91f4195 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/HgTest.java @@ -0,0 +1,57 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +import ai.chat2db.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class HgTest { + private static final Logger LOGGER = LoggerFactory.getLogger(HgTest.class); + + @Test + public void hh() throws IOException { + List list = + EasyExcel.read(new FileInputStream("D:\\test\\201909301017rule.xlsx")).headRowNumber(2).sheet() + .doReadSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void hh5() throws IOException { + URL url = new URL( + "http://hotelcontractfil.oss-cn-beijing.aliyuncs" + + ".com/2019/%E5%98%89%E6%83%A0-%E4%B8%AD%E4%BA%A4%E5%BB%BA_2019-09-01_2019-09-30_1569055677522" + + ".xlsx?Expires=1884415681&OSSAccessKeyId=LTAIGZDkqZfPArBr&Signature=Rf0gbO8vl3l%2Brj1KdyzHHMsUhCE" + + "%3D"); + InputStream is = url.openStream(); + List list = + EasyExcel.read(is).headRowNumber(0).sheet().doReadSync(); + for (Object data : list) { + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void hh2() throws IOException { + EasyExcel.read(new FileInputStream("D:\\test\\商户不匹配工单信息收集表格.xlsx")).registerReadListener( + new HgListener()) + .headRowNumber(0).sheet().doRead(); + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/JsonData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/JsonData.java new file mode 100644 index 0000000..2b81996 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/JsonData.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.test.temp.simple; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * TODO + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class JsonData { + private String SS1; + private String sS2; + private String ss3; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatListener.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatListener.java new file mode 100644 index 0000000..59268d8 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatListener.java @@ -0,0 +1,51 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.ArrayList; +import java.util.List; + +import ai.chat2db.excel.event.AnalysisEventListener; +import ai.chat2db.excel.test.demo.read.DemoDataListener; +import ai.chat2db.excel.test.temp.LockData; +import ai.chat2db.excel.context.AnalysisContext; +import com.alibaba.fastjson2.JSON; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class RepeatListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); + /** + * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + List list = new ArrayList(); + + @Override + public void invoke(LockData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + list.add(data); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + LOGGER.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatTest.java new file mode 100644 index 0000000..19269e3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/RepeatTest.java @@ -0,0 +1,55 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.io.FileInputStream; +import java.io.IOException; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelReader; +import ai.chat2db.excel.read.metadata.ReadSheet; +import ai.chat2db.excel.test.temp.LockData; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +public class RepeatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(RepeatTest.class); + + @Test + public void hh() throws IOException { + ExcelReader reader = + EasyExcel.read(new FileInputStream("D:\\test\\hg2.xls"), LockData.class, new RepeatListener()) + .headRowNumber(0).build(); + ReadSheet r1 = EasyExcel.readSheet(0).build(); + ReadSheet r2 = EasyExcel.readSheet(2).build(); + reader.read(r1); + reader.read(r2); + reader.finish(); + } + + @Test + public void hh2() throws IOException { + ExcelReader reader = + EasyExcel.read(new FileInputStream("D:\\test\\sheet.xls"), LockData.class, new RepeatListener()) + .headRowNumber(0).build(); + ReadSheet r2 = EasyExcel.readSheet(1).build(); + reader.read(r2); + reader.finish(); + } + + @Test + public void hh1() throws IOException { + ExcelReader reader = + EasyExcel.read(new FileInputStream("D:\\test\\hg2.xls"), LockData.class, new RepeatListener()) + .headRowNumber(0).build(); + ReadSheet r2 = EasyExcel.readSheet(0).build(); + reader.read(r2); + reader.finish(); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/Write.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/Write.java new file mode 100644 index 0000000..1ba66e3 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/Write.java @@ -0,0 +1,148 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.ExcelWriter; +import ai.chat2db.excel.test.core.large.LargeData; +import ai.chat2db.excel.test.demo.write.DemoData; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.write.metadata.WriteSheet; +import ai.chat2db.excel.write.metadata.WriteTable; +import com.alibaba.fastjson2.JSON; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ + +@Slf4j +public class Write { + private static final Logger LOGGER = LoggerFactory.getLogger(Write.class); + + @Test + public void simpleWrite1() { + LargeData ss = new LargeData(); + ss.setStr23("ttt"); + Map map = BeanMapUtils.create(ss); + System.out.println(map.containsKey("str23")); + System.out.println(map.containsKey("str22")); + System.out.println(map.get("str23")); + System.out.println(map.get("str22")); + } + + @Test + public void simpleWrite() { + log.info("t5"); + // 写法1 + String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, DemoData.class).relativeHeadRowIndex(10).sheet("模板").doWrite(data()); + } + + @Test + public void simpleWrite2() { + // 写法1 + String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, WriteData.class).sheet("模板").registerWriteHandler(new WriteHandler()).doWrite( + data1()); + } + + @Test + public void simpleWrite3() { + // 写法1 + String fileName = TestFileUtil.getPath() + "t33" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName).head(head()).inMemory(true).sheet("模板").registerWriteHandler(new WriteCellHandler()) + .doWrite( + data1()); + } + + @Test + public void json() { + JsonData jsonData = new JsonData(); + jsonData.setSS1("11"); + jsonData.setSS2("22"); + jsonData.setSs3("33"); + System.out.println(JSON.toJSONString(jsonData)); + + } + + @Test + public void json3() { + String json = "{\"SS1\":\"11\",\"sS2\":\"22\",\"ss3\":\"33\"}"; + + JsonData jsonData = JSON.parseObject(json, JsonData.class); + System.out.println(JSON.toJSONString(jsonData)); + + } + + @Test + public void tableWrite() { + String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例 + // 这里 需要指定写用哪个class去写 + ExcelWriter excelWriter = EasyExcel.write(fileName).build(); + // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 + WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); + // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要 + WriteTable writeTable0 = EasyExcel.writerTable(0).head(DemoData1.class).build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet, writeTable0); + // 第二次写如也会创建头,然后在第一次的后面写入数据 + /// 千万别忘记close 会帮忙关闭流 + excelWriter.finish(); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = new ArrayList(); + head0.add("字符串" + System.currentTimeMillis()); + List head1 = new ArrayList(); + head1.add("数字" + System.currentTimeMillis()); + List head2 = new ArrayList(); + head2.add("日期" + System.currentTimeMillis()); + list.add(head0); + list.add(head1); + list.add(head2); + return list; + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + DemoData data = new DemoData(); + data.setString("640121807369666560" + i); + data.setDate(new Date()); + data.setDoubleData(null); + list.add(data); + } + return list; + } + + private List data1() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + WriteData data = new WriteData(); + data.setDd(new Date()); + data.setF1(33f); + list.add(data); + } + return list; + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteCellHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteCellHandler.java new file mode 100644 index 0000000..455a1f2 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteCellHandler.java @@ -0,0 +1,39 @@ +package ai.chat2db.excel.test.temp.simple; + +import ai.chat2db.excel.metadata.Head; +import ai.chat2db.excel.metadata.data.WriteCellData; +import ai.chat2db.excel.write.handler.CellWriteHandler; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteTableHolder; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.IndexedColors; + +/** + * @author Jiaju Zhuang + */ +@Slf4j +public class WriteCellHandler implements CellWriteHandler { + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + WriteCellData cellData, Cell cell, Head head, Integer integer, Boolean isHead) { + + if (!isHead) { + CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper(); + CellStyle cellStyle = writeSheetHolder.getSheet().getWorkbook().createCellStyle(); + if (cellStyle != null) { + DataFormat dataFormat = createHelper.createDataFormat(); + cellStyle.setWrapText(true); + cellStyle.setFillBackgroundColor(IndexedColors.RED.getIndex()); + cellStyle.setBottomBorderColor(IndexedColors.RED.getIndex()); + cellStyle.setDataFormat(dataFormat.getFormat("yyyy-MM-dd")); + cell.setCellStyle(cellStyle); + } + } + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteData.java new file mode 100644 index 0000000..813f260 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteData.java @@ -0,0 +1,22 @@ +package ai.chat2db.excel.test.temp.simple; + +import java.util.Date; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * write data + * + * @author Jiaju Zhuang + **/ +@Getter +@Setter +@EqualsAndHashCode +public class WriteData { + // @ContentStyle(locked = true) + private Date dd; + // @ContentStyle(locked = false) + private float f1; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteHandler.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteHandler.java new file mode 100644 index 0000000..e25a21b --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/simple/WriteHandler.java @@ -0,0 +1,21 @@ +package ai.chat2db.excel.test.temp.simple; + +import ai.chat2db.excel.write.handler.SheetWriteHandler; +import ai.chat2db.excel.write.metadata.holder.WriteSheetHolder; +import ai.chat2db.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author Jiaju Zhuang + */ +@Slf4j +public class WriteHandler implements SheetWriteHandler { + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, + WriteSheetHolder writeSheetHolder) { + log.info("锁住"); + writeSheetHolder.getSheet().protectSheet("edit"); + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteData.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteData.java new file mode 100644 index 0000000..5341187 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteData.java @@ -0,0 +1,19 @@ +package ai.chat2db.excel.test.temp.write; + +import ai.chat2db.excel.annotation.ExcelProperty; +import ai.chat2db.excel.annotation.write.style.ContentStyle; +import ai.chat2db.excel.annotation.write.style.HeadStyle; +import ai.chat2db.excel.enums.BooleanEnum; + +import lombok.Data; + +@Data +//@Accessors(chain = true) +public class TempWriteData { + private String name1; + + @ExcelProperty(" 换行\r\n \\ \r\n的名字") + @HeadStyle(wrapped = BooleanEnum.TRUE) + @ContentStyle(wrapped = BooleanEnum.TRUE) + private String name; +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteTest.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteTest.java new file mode 100644 index 0000000..73f1175 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/temp/write/TempWriteTest.java @@ -0,0 +1,162 @@ +package ai.chat2db.excel.test.temp.write; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; + +import ai.chat2db.excel.EasyExcel; +import ai.chat2db.excel.test.demo.read.CustomStringStringConverter; +import ai.chat2db.excel.util.BeanMapUtils; +import ai.chat2db.excel.util.FileUtils; +import ai.chat2db.excel.util.ListUtils; +import ai.chat2db.excel.test.util.TestFileUtil; +import ai.chat2db.excel.support.cglib.beans.BeanMap; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Picture; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFCell; +import org.apache.poi.xssf.streaming.SXSSFDrawing; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; + +@Slf4j +public class TempWriteTest { + + @Test + public void write() { + TempWriteData tempWriteData = new TempWriteData(); + tempWriteData.setName("zs\r\n \\ \r\n t4"); + EasyExcel.write(TestFileUtil.getPath() + "TempWriteTest" + System.currentTimeMillis() + ".xlsx", + TempWriteData.class) + .sheet() + .registerConverter(new CustomStringStringConverter()) + .doWrite(ListUtils.newArrayList(tempWriteData)); + + EasyExcel.write(TestFileUtil.getPath() + "TempWriteTest" + System.currentTimeMillis() + ".xlsx", + TempWriteData.class) + .sheet() + .doWrite(ListUtils.newArrayList(tempWriteData)); + + } + + @Test + public void cglib() { + TempWriteData tempWriteData = new TempWriteData(); + tempWriteData.setName("1"); + tempWriteData.setName1("2"); + BeanMap beanMap = BeanMapUtils.create(tempWriteData); + + log.info("d1{}", beanMap.get("name")); + log.info("d2{}", beanMap.get("name1")); + + TempWriteData tempWriteData2 = new TempWriteData(); + + Map map = new HashMap<>(); + map.put("name", "zs"); + BeanMap beanMap2 = BeanMapUtils.create(tempWriteData2); + beanMap2.putAll(map); + log.info("3{}", tempWriteData2.getName()); + + } + + @Test + public void imageWrite() throws Exception { + //String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx"; + // + //// 这里 需要指定写用哪个class去写 + //try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { + // // 这里注意 如果同一个sheet只要创建一次 + // WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); + // // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来 + // for (int i = 0; i < 5; i++) { + // // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + // List data = data(); + // excelWriter.write(data, writeSheet); + // } + //} + } + + @Test + public void imageWritePoi() throws Exception { + String file = "/Users/zhuangjiaju/test/imagetest" + System.currentTimeMillis() + ".xlsx"; + SXSSFWorkbook workbook = new SXSSFWorkbook(); + SXSSFSheet sheet = workbook.createSheet("测试"); + CreationHelper helper = workbook.getCreationHelper(); + SXSSFDrawing sxssfDrawin = sheet.createDrawingPatriarch(); + + byte[] imagebyte = FileUtils.readFileToByteArray(new File("/Users/zhuangjiaju/Documents/demo.jpg")); + + for (int i = 0; i < 1 * 10000; i++) { + SXSSFRow row = sheet.createRow(i); + SXSSFCell cell = row.createCell(0); + cell.setCellValue(i); + int pictureIdx = workbook.addPicture(imagebyte, Workbook.PICTURE_TYPE_JPEG); + ClientAnchor anchor = helper.createClientAnchor(); + anchor.setCol1(0); + anchor.setRow1(i); + // 插入图片 + Picture pict = sxssfDrawin.createPicture(anchor, pictureIdx); + pict.resize(); + log.info("新增行:{}", i); + } + FileOutputStream fileOutputStream = new FileOutputStream(file); + workbook.write(fileOutputStream); + fileOutputStream.flush(); + workbook.close(); + } + + @Test + public void tep() throws Exception { + String file = "/Users/zhuangjiaju/test/imagetest" + System.currentTimeMillis() + ".xlsx"; + SXSSFWorkbook workbook = new SXSSFWorkbook(); + SXSSFSheet sheet = workbook.createSheet("测试"); + CreationHelper helper = workbook.getCreationHelper(); + SXSSFDrawing sxssfDrawin = sheet.createDrawingPatriarch(); + + byte[] imagebyte = FileUtils.readFileToByteArray(new File("/Users/zhuangjiaju/Documents/demo.jpg")); + + for (int i = 0; i < 1 * 10000; i++) { + SXSSFRow row = sheet.createRow(i); + SXSSFCell cell = row.createCell(0); + cell.setCellValue(i); + int pictureIdx = workbook.addPicture(imagebyte, Workbook.PICTURE_TYPE_JPEG); + ClientAnchor anchor = helper.createClientAnchor(); + anchor.setCol1(0); + anchor.setRow1(i); + // 插入图片 + Picture pict = sxssfDrawin.createPicture(anchor, pictureIdx); + pict.resize(); + log.info("新增行:{}", i); + } + FileOutputStream fileOutputStream = new FileOutputStream(file); + workbook.write(fileOutputStream); + fileOutputStream.flush(); + workbook.close(); + } + + @Test + public void large() throws Exception { + String file = "/Users/zhuangjiaju/test/imagetest" + System.currentTimeMillis() + ".xlsx"; + SXSSFWorkbook workbook = new SXSSFWorkbook(new XSSFWorkbook( + new File( + "/Users/zhuangjiaju/IdeaProjects/easyexcel/easyexcel-test/src/test/resources/large/large07.xlsx"))); + SXSSFSheet sheet = workbook.createSheet("测试"); + + SXSSFRow row = sheet.createRow(500000); + SXSSFCell cell = row.createCell(0); + cell.setCellValue("test"); + + FileOutputStream fileOutputStream = new FileOutputStream(file); + workbook.write(fileOutputStream); + fileOutputStream.flush(); + workbook.close(); + + } +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestFileUtil.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestFileUtil.java new file mode 100644 index 0000000..b1070dd --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestFileUtil.java @@ -0,0 +1,76 @@ +package ai.chat2db.excel.test.util; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +public class TestFileUtil { + + public static InputStream getResourcesFileInputStream(String fileName) { + return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName); + } + + public static String getPath() { + return TestFileUtil.class.getResource("/").getPath(); + } + + public static TestPathBuild pathBuild() { + return new TestPathBuild(); + } + + public static File createNewFile(String pathName) { + File file = new File(getPath() + pathName); + if (file.exists()) { + file.delete(); + } else { + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + } + return file; + } + + public static File readFile(String pathName) { + return new File(getPath() + pathName); + } + + public static File readUserHomeFile(String pathName) { + return new File(System.getProperty("user.home") + File.separator + pathName); + } + + /** + * build to test file path + **/ + public static class TestPathBuild { + private TestPathBuild() { + subPath = new ArrayList<>(); + } + + private final List subPath; + + public TestPathBuild sub(String dirOrFile) { + subPath.add(dirOrFile); + return this; + } + + public String getPath() { + if (CollectionUtils.isEmpty(subPath)) { + return TestFileUtil.class.getResource("/").getPath(); + } + if (subPath.size() == 1) { + return TestFileUtil.class.getResource("/").getPath() + subPath.get(0); + } + StringBuilder path = new StringBuilder(TestFileUtil.class.getResource("/").getPath()); + path.append(subPath.get(0)); + for (int i = 1; i < subPath.size(); i++) { + path.append(File.separator).append(subPath.get(i)); + } + return path.toString(); + } + + } + +} diff --git a/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestUtil.java b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestUtil.java new file mode 100644 index 0000000..a8eb9e0 --- /dev/null +++ b/easyexcel-plus-test/src/test/java/ai/chat2db/excel/test/util/TestUtil.java @@ -0,0 +1,33 @@ +package ai.chat2db.excel.test.util; + +import java.text.ParseException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; + +import ai.chat2db.excel.util.DateUtils; + +import lombok.extern.slf4j.Slf4j; + +/** + * test util + * + * @author Jiaju Zhuang + */ +@Slf4j +public class TestUtil { + + public static final Date TEST_DATE; + public static final LocalDate TEST_LOCAL_DATE = LocalDate.of(2020, 1, 1); + public static final LocalDateTime TEST_LOCAL_DATE_TIME = LocalDateTime.of(2020, 1, 1, 1, 1, 1); + + static { + try { + TEST_DATE = DateUtils.parseDate("2020-01-01 01:01:01"); + } catch (ParseException e) { + log.error("init TestUtil error.", e); + throw new RuntimeException(e); + } + } + +} diff --git a/easyexcel-plus-test/src/test/resources/bom/no_bom.csv b/easyexcel-plus-test/src/test/resources/bom/no_bom.csv new file mode 100644 index 0000000..461c1eb --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/bom/no_bom.csv @@ -0,0 +1,11 @@ +姓名,年纪 +姓名0,20 +姓名1,20 +姓名2,20 +姓名3,20 +姓名4,20 +姓名5,20 +姓名6,20 +姓名7,20 +姓名8,20 +姓名9,20 \ No newline at end of file diff --git a/easyexcel-plus-test/src/test/resources/bom/office_bom.csv b/easyexcel-plus-test/src/test/resources/bom/office_bom.csv new file mode 100644 index 0000000..b6a8099 --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/bom/office_bom.csv @@ -0,0 +1,11 @@ +姓名,年纪 +姓名0,20 +姓名1,20 +姓名2,20 +姓名3,20 +姓名4,20 +姓名5,20 +姓名6,20 +姓名7,20 +姓名8,20 +姓名9,20 \ No newline at end of file diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t01.xls b/easyexcel-plus-test/src/test/resources/compatibility/t01.xls new file mode 100644 index 0000000..eb0782f Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t01.xls differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t02.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t02.xlsx new file mode 100644 index 0000000..b8d755d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t02.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t03.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t03.xlsx new file mode 100644 index 0000000..3a31ef7 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t03.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t04.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t04.xlsx new file mode 100644 index 0000000..7c95d42 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t04.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t05.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t05.xlsx new file mode 100644 index 0000000..248ec7d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t05.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t06.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t06.xlsx new file mode 100644 index 0000000..b27be02 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t06.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t07.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t07.xlsx new file mode 100644 index 0000000..a7b0eac Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t07.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/compatibility/t09.xlsx b/easyexcel-plus-test/src/test/resources/compatibility/t09.xlsx new file mode 100644 index 0000000..0b29141 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/compatibility/t09.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/converter/converter03.xls b/easyexcel-plus-test/src/test/resources/converter/converter03.xls new file mode 100644 index 0000000..89c2ab6 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/converter/converter03.xls differ diff --git a/easyexcel-plus-test/src/test/resources/converter/converter07.xlsx b/easyexcel-plus-test/src/test/resources/converter/converter07.xlsx new file mode 100644 index 0000000..99ace69 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/converter/converter07.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/converter/converterCsv.csv b/easyexcel-plus-test/src/test/resources/converter/converterCsv.csv new file mode 100644 index 0000000..01bcf2e --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/converter/converterCsv.csv @@ -0,0 +1,2 @@ +大数的布尔(不支持),大数的数字,大数的字符串,大整数的布尔(不支持),大整数的数字,大整数的字符串,布尔的布尔,布尔的数字(不支持),布尔的字符串,字节的布尔(不支持),字节的数字,字节的字符串,日期的数字,日期的字符串,本地日期的数字,本地日期的字符串,双精度浮点的布尔(不支持),双精度浮点的数字,双精度浮点的字符串,浮点的布尔(不支持),浮点的数字,浮点的字符串,整型的布尔(不支持),整型的数字,整型的字符串,长整型的布尔(不支持),长整型的数字,长整型的字符串,短整型的布尔(不支持),短整型的数字,短整型的字符串,字符串的布尔,字符串的数字,字符串的字符串,字符串的错误,字符串的数字公式,字符串的字符串公式,字符串的数字日期 +1,1,1,1,1,1,TRUE,TRUE,TRUE,1,1,1,2020-01-01 01:01:01,2020-01-01 01:01:01,2020-01-01 01:01:01,2020-01-01 01:01:01,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,TRUE,1,测试,#VALUE!,2,1测试,2020-01-01 01:01:01 \ No newline at end of file diff --git a/easyexcel-plus-test/src/test/resources/converter/img.jpg b/easyexcel-plus-test/src/test/resources/converter/img.jpg new file mode 100644 index 0000000..953f39f Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/converter/img.jpg differ diff --git a/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xls b/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xls new file mode 100644 index 0000000..95c306c Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xls differ diff --git a/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xlsx b/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xlsx new file mode 100644 index 0000000..34a05cc Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/dataformat/dataformat.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/dataformat/dataformatv2.xlsx b/easyexcel-plus-test/src/test/resources/dataformat/dataformatv2.xlsx new file mode 100644 index 0000000..c49aa04 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/dataformat/dataformatv2.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/cellDataDemo.xlsx b/easyexcel-plus-test/src/test/resources/demo/cellDataDemo.xlsx new file mode 100644 index 0000000..947229c Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/cellDataDemo.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/demo.csv b/easyexcel-plus-test/src/test/resources/demo/demo.csv new file mode 100644 index 0000000..97f2a42 --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/demo/demo.csv @@ -0,0 +1,11 @@ +字符串标题,日期标题,数字标题 +字符串0,2020-01-01 01:01:00,1 +字符串1,2020-01-02 01:01:00,2 +字符串2,2020-01-03 01:01:00,3 +字符串3,2020-01-04 01:01:00,4 +字符串4,2020-01-05 01:01:00,5 +字符串5,2020-01-06 01:01:00,6 +字符串6,2020-01-07 01:01:00,7 +字符串7,2020-01-08 01:01:00,8 +字符串8,2020-01-09 01:01:00,9 +字符串9,2020-01-10 01:01:00,10 \ No newline at end of file diff --git a/easyexcel-plus-test/src/test/resources/demo/demo.xlsx b/easyexcel-plus-test/src/test/resources/demo/demo.xlsx new file mode 100644 index 0000000..662bb97 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/demo.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/extra.xlsx b/easyexcel-plus-test/src/test/resources/demo/extra.xlsx new file mode 100644 index 0000000..4936b05 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/extra.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/complex.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/complex.xlsx new file mode 100644 index 0000000..5376713 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/complex.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/complexFillWithTable.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/complexFillWithTable.xlsx new file mode 100644 index 0000000..4de1a1e Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/complexFillWithTable.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/composite.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/composite.xlsx new file mode 100644 index 0000000..c76a2b1 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/composite.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/horizontal.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/horizontal.xlsx new file mode 100644 index 0000000..c8b4564 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/horizontal.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/list.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/list.xlsx new file mode 100644 index 0000000..d29e05e Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/list.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/demo/fill/simple.xlsx b/easyexcel-plus-test/src/test/resources/demo/fill/simple.xlsx new file mode 100644 index 0000000..7514d1d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/demo/fill/simple.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/extra/extra.xls b/easyexcel-plus-test/src/test/resources/extra/extra.xls new file mode 100644 index 0000000..89f389b Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/extra/extra.xls differ diff --git a/easyexcel-plus-test/src/test/resources/extra/extra.xlsx b/easyexcel-plus-test/src/test/resources/extra/extra.xlsx new file mode 100644 index 0000000..4936b05 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/extra/extra.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/extra/extraRelationships.xlsx b/easyexcel-plus-test/src/test/resources/extra/extraRelationships.xlsx new file mode 100644 index 0000000..5784cd8 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/extra/extraRelationships.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/annotation.xls b/easyexcel-plus-test/src/test/resources/fill/annotation.xls new file mode 100644 index 0000000..de09678 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/annotation.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/annotation.xlsx b/easyexcel-plus-test/src/test/resources/fill/annotation.xlsx new file mode 100644 index 0000000..2a4a92b Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/annotation.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/byName.xls b/easyexcel-plus-test/src/test/resources/fill/byName.xls new file mode 100644 index 0000000..e07fd50 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/byName.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/byName.xlsx b/easyexcel-plus-test/src/test/resources/fill/byName.xlsx new file mode 100644 index 0000000..327e055 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/byName.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/complex.xls b/easyexcel-plus-test/src/test/resources/fill/complex.xls new file mode 100644 index 0000000..d575895 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/complex.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/complex.xlsx b/easyexcel-plus-test/src/test/resources/fill/complex.xlsx new file mode 100644 index 0000000..5376713 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/complex.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/composite.xls b/easyexcel-plus-test/src/test/resources/fill/composite.xls new file mode 100644 index 0000000..e48aa0c Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/composite.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/composite.xlsx b/easyexcel-plus-test/src/test/resources/fill/composite.xlsx new file mode 100644 index 0000000..c76a2b1 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/composite.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/horizontal.xls b/easyexcel-plus-test/src/test/resources/fill/horizontal.xls new file mode 100644 index 0000000..570f901 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/horizontal.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/horizontal.xlsx b/easyexcel-plus-test/src/test/resources/fill/horizontal.xlsx new file mode 100644 index 0000000..c8b4564 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/horizontal.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/simple.csv b/easyexcel-plus-test/src/test/resources/fill/simple.csv new file mode 100644 index 0000000..a515348 --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/fill/simple.csv @@ -0,0 +1,2 @@ +姓名,数字,复杂,忽略,空 +{name},{number},{name}今年{number}岁了,\{name\}忽略,{name},空{.empty} \ No newline at end of file diff --git a/easyexcel-plus-test/src/test/resources/fill/simple.xls b/easyexcel-plus-test/src/test/resources/fill/simple.xls new file mode 100644 index 0000000..317ef6d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/simple.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/simple.xlsx b/easyexcel-plus-test/src/test/resources/fill/simple.xlsx new file mode 100644 index 0000000..a441eba Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/simple.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/fill/style.xls b/easyexcel-plus-test/src/test/resources/fill/style.xls new file mode 100644 index 0000000..3127743 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/style.xls differ diff --git a/easyexcel-plus-test/src/test/resources/fill/style.xlsx b/easyexcel-plus-test/src/test/resources/fill/style.xlsx new file mode 100644 index 0000000..062540d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/fill/style.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/large/fill.xlsx b/easyexcel-plus-test/src/test/resources/large/fill.xlsx new file mode 100644 index 0000000..c3c376d Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/large/fill.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/large/large07.xlsx b/easyexcel-plus-test/src/test/resources/large/large07.xlsx new file mode 100644 index 0000000..a317e71 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/large/large07.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/logback.xml b/easyexcel-plus-test/src/test/resources/logback.xml new file mode 100644 index 0000000..74ebe75 --- /dev/null +++ b/easyexcel-plus-test/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + + ${CONSOLE_LOG_PATTERN} + utf8 + + + + + + + diff --git a/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xls b/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xls new file mode 100644 index 0000000..a560128 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xls differ diff --git a/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xlsx b/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xlsx new file mode 100644 index 0000000..f90680a Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/multiplesheets/multiplesheets.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/simple/simple07.xlsx b/easyexcel-plus-test/src/test/resources/simple/simple07.xlsx new file mode 100644 index 0000000..3d25fcd Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/simple/simple07.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/temp/issue1663/template.xlsx b/easyexcel-plus-test/src/test/resources/temp/issue1663/template.xlsx new file mode 100644 index 0000000..a968ff4 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/temp/issue1663/template.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/temp/issue2443/date1.xlsx b/easyexcel-plus-test/src/test/resources/temp/issue2443/date1.xlsx new file mode 100644 index 0000000..92ef811 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/temp/issue2443/date1.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/temp/issue2443/date2.xlsx b/easyexcel-plus-test/src/test/resources/temp/issue2443/date2.xlsx new file mode 100644 index 0000000..c6feb32 Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/temp/issue2443/date2.xlsx differ diff --git a/easyexcel-plus-test/src/test/resources/template/template03.xls b/easyexcel-plus-test/src/test/resources/template/template03.xls new file mode 100644 index 0000000..7c17eee Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/template/template03.xls differ diff --git a/easyexcel-plus-test/src/test/resources/template/template07.xlsx b/easyexcel-plus-test/src/test/resources/template/template07.xlsx new file mode 100644 index 0000000..a046fbc Binary files /dev/null and b/easyexcel-plus-test/src/test/resources/template/template07.xlsx differ diff --git a/easyexcel-plus/pom.xml b/easyexcel-plus/pom.xml new file mode 100644 index 0000000..ce1e087 --- /dev/null +++ b/easyexcel-plus/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + + ai.chat2db.excel + easyexcel-plus-parent + 1.0.0-SNAPSHOT + ../pom.xml + + + https://github.com/alibaba/easyexcel + jar + easyexcel-plus + easyexcel-plus + + + + ai.chat2db.excel + easyexcel-plus-core + 1.0.0-SNAPSHOT + + + + diff --git a/easyexcel-plus/src/main/java/plus/easyexcel/Empty.java b/easyexcel-plus/src/main/java/plus/easyexcel/Empty.java new file mode 100644 index 0000000..05468b0 --- /dev/null +++ b/easyexcel-plus/src/main/java/plus/easyexcel/Empty.java @@ -0,0 +1,9 @@ +package plus.easyexcel; + +/** + * empty + * + * @author Jiaju Zhuang + */ +public class Empty { +} diff --git a/easyexcel_en.md b/easyexcel_en.md new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/easyexcel_en.md @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/img/readme/large.png b/img/readme/large.png new file mode 100644 index 0000000..0188af9 Binary files /dev/null and b/img/readme/large.png differ diff --git a/img/style/eclipse/step.jpg b/img/style/eclipse/step.jpg new file mode 100644 index 0000000..fcbb857 Binary files /dev/null and b/img/style/eclipse/step.jpg differ diff --git a/img/style/idea/step1.png b/img/style/idea/step1.png new file mode 100644 index 0000000..d4a39f5 Binary files /dev/null and b/img/style/idea/step1.png differ diff --git a/img/style/idea/step2.png b/img/style/idea/step2.png new file mode 100644 index 0000000..2dd0414 Binary files /dev/null and b/img/style/idea/step2.png differ diff --git a/img/style/idea/step3.png b/img/style/idea/step3.png new file mode 100644 index 0000000..2fbde4a Binary files /dev/null and b/img/style/idea/step3.png differ diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..50dfe61 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +lombok.toString.callSuper = CALL +lombok.equalsAndHashCode.callSuper= CALL \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..5643201 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..8a15b7f --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..be78d64 --- /dev/null +++ b/pom.xml @@ -0,0 +1,287 @@ + + + 4.0.0 + ai.chat2db.excel + easyexcel-plus-parent + pom + 1.0.0-SNAPSHOT + easyexcel-plus-parent + easyexcel plus is a excel handle tools written in Java + https://github.com/CodePhiliaX/easyexcel-plus + 2024 + + + easyexcel-plus-core + easyexcel-plus-support + easyexcel-plus-test + easyexcel-plus + + + + + 1.0.0-SNAPSHOT + UTF-8 + 1.8 + true + true + true + + + + https://github.com/alibaba/easyexcel + scm:git:https://git@github.com/alibaba/easyexcel.git + + + + + Alibaba Group + https://github.com/alibaba + + + + + jipengfei + jipengfei + 1558143046@qq.com + + + zhuangjiaju + Jiaju Zhuang + zhuangjiaju@qq.com + + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.0 + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + org.codehaus.mojo + flatten-maven-plugin + 1.2.7 + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + + true + + + + compile + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + verify + + sign + + + + + + + --pinentry-mode + loopback + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + org.projectlombok + lombok-maven-plugin + + + generate-sources + + delombok + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + true + oss + + + + flatten.process-resources + process-resources + + flatten + + + + flatten + package + + flatten + + + + flatten.clean + clean + + clean + + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://oss.sonatype.org/ + true + + + + + diff --git a/quickstart.md b/quickstart.md new file mode 100644 index 0000000..dcf12ad --- /dev/null +++ b/quickstart.md @@ -0,0 +1,2 @@ +# 请登录官网查看 +官网地址:[https://alibaba-easyexcel.github.io](https://alibaba-easyexcel.github.io) \ No newline at end of file diff --git a/src/test/java/plus/easyexcel/test/demo/fill/FillTest.java b/src/test/java/plus/easyexcel/test/demo/fill/FillTest.java new file mode 100644 index 0000000..b356c58 --- /dev/null +++ b/src/test/java/plus/easyexcel/test/demo/fill/FillTest.java @@ -0,0 +1,3 @@ +/** + * Documents have been migrated to https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java + */ diff --git a/src/test/java/plus/easyexcel/test/demo/read/ReadTest.java b/src/test/java/plus/easyexcel/test/demo/read/ReadTest.java new file mode 100644 index 0000000..7a9213e --- /dev/null +++ b/src/test/java/plus/easyexcel/test/demo/read/ReadTest.java @@ -0,0 +1,3 @@ +/** + * Documents have been migrated to https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java + */ diff --git a/src/test/java/plus/easyexcel/test/demo/web/WebTest.java b/src/test/java/plus/easyexcel/test/demo/web/WebTest.java new file mode 100644 index 0000000..86ceefb --- /dev/null +++ b/src/test/java/plus/easyexcel/test/demo/web/WebTest.java @@ -0,0 +1,3 @@ +/** + * Documents have been migrated to https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java + */ diff --git a/src/test/java/plus/easyexcel/test/demo/write/WriteTest.java b/src/test/java/plus/easyexcel/test/demo/write/WriteTest.java new file mode 100644 index 0000000..61bd164 --- /dev/null +++ b/src/test/java/plus/easyexcel/test/demo/write/WriteTest.java @@ -0,0 +1,3 @@ +/** + * Documents have been migrated to https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java + */ diff --git a/style/STYLE.md b/style/STYLE.md new file mode 100644 index 0000000..cc75b44 --- /dev/null +++ b/style/STYLE.md @@ -0,0 +1,29 @@ +## 统一代码风格 +如果要编辑本项目,一定要统一代码风格 +### 统一方案 +本工程代码遵守阿里巴巴[p3c](https://github.com/alibaba/p3c)规范,在代码开发前建议: +* 安装阿里巴巴规约插件,用于提早发现不规范代码,具体安装方法参照:[p3c](https://github.com/alibaba/p3c),里面有eclipse,idea的安装方法 +* 安装codeStyle插件,用于格式化代码的时候符合代码规范,安装方法见:[安装codeStyle插件](#codeStyleInstall) +* checkStyle校验,这个不用安装,`mvn`在编译的时候自己会调用,写在`pom.xml`里,用`maven-pmd-plugin`调用`pmd-p3c`规范校验 + +如果工作中遇到代码格式化问题,经常导致git冲突,也可以采用上面的方案 +### codeStyle插件安装 +这里注意下,不管eclipse或者idea,都需要导入eclipse文件夹下面的配置 +#### eclipse +* 依次点击:`Window->Preferences->Java->Code Style->Formatter->Import` +* 选择`style/eclipse/codestyle.xml`文件 确定 +* 默认在`Active profile`中选择新导入的`P3C-CodeStyle`,如未选择,请手动选择 +* 点击`Apply`完成配置 +![step](../img/style/eclipse/step.jpg) +#### idea +* 依次点击进入插件界面:`File->Settings->Plugins`,搜索 eclipse code formatter,如已有插件则不需安装,如没有,点击Search in repositories自动搜索线上插件。 +![step1](../img/style/idea/step1.png) +* 导入`style/eclipse/codestyle.xml` 这里记住用的也是eclipse里面的 点击OK +![step1](../img/style/idea/step2.png) +* 依次点击进入插件界面:`File->Settings->Editor->Code Style->Java->Import Scheme->Intellij IDEA code style XML`,导入`style/idea/codestyle.xml` 这里用的是idea的配置文件 +![step1](../img/style/idea/step3.png) +* 完成 + +### 附 +#### windows系统,代码规范校验提示乱码 +这个是因为cmd默认gbk,cmd输入:`CHCP 65001` 在运行mvn 提示就是不会乱码了 \ No newline at end of file diff --git a/style/codestyle/eclipse/codestyle.xml b/style/codestyle/eclipse/codestyle.xml new file mode 100644 index 0000000..42b3055 --- /dev/null +++ b/style/codestyle/eclipse/codestyle.xml @@ -0,0 +1,603 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/style/codestyle/idea/codestyle.xml b/style/codestyle/idea/codestyle.xml new file mode 100644 index 0000000..5ee5b77 --- /dev/null +++ b/style/codestyle/idea/codestyle.xml @@ -0,0 +1,91 @@ + + + \ No newline at end of file diff --git a/update.md b/update.md new file mode 100644 index 0000000..c31e8bd --- /dev/null +++ b/update.md @@ -0,0 +1,455 @@ +# 4.0.3 + +* 兼容部分日期格式读取异常的问题 + +# 4.0.2 + +* 兼容某些特殊的xls: 修改了内置的样式导致判断样式错误 +* 重新加回 `commons-io` + +# 4.0.1 + +* `commons-io` 修改为依赖 `poi`的版本 +* 修复临时目录被清理可能提示`NoSuchFileException`的异常 + +# 4.0.0 + +* `poi`由`4.1.2`升级到`5.2.5` +* `commons-csv`由`1.1.0`升级到`1.10.0` +* `slf4j-api`由`1.7.32`升级到`1.7.36` +* `ehcache`由`3.9.9`升级到`3.9.11` +* 支持`jdk21` + +# 3.3.4 + +* 支持停止单个`sheet`以后继续读取其他`sheet`,使用`ExcelAnalysisStopSheetException` + +# 3.3.3 + +* 兼容某些特殊的xls: 缺少每个sheet的终止符 + +# 3.3.2 + +* 修复`includeColumnIndexes`和`includeColumnFieldNames`在sheet后面失效的问题 + +# 3.3.1 + +* 修改版本发布问题 + +# 3.3.0 + +* 读csv会忽略BOM数据 [Issue #3137](https://github.com/alibaba/easyexcel/issues/3137) +* 解决csv用office打开乱码的问题,写csv默认带上BOM数据 +* xlsx存在隐藏字符时需要忽略,确保和展示看到的一样 +* 新增`commons-io` 2.11.0 包 +* 在`easyexcel-parent` 包中移除测试包的`dependencyManagement` +* 删除`org.apache.poi.hssf.usermodel.PoiUtils.java`, + 使用反射获取 [Issue #2804](https://github.com/alibaba/easyexcel/issues/2804) +* 默认对象反射缓存改成`ThreadLocal` + ,并支持设置反射缓存类型 [Issue #2792](https://github.com/alibaba/easyexcel/issues/2792) +* 支持根据`includeColumnIndexes`和`includeColumnFieldNames` + 排序 [Issue #2697](https://github.com/alibaba/easyexcel/issues/2697) +* 根据文件流解析,由抛出异常改为,默认识别为csv + +# 3.2.1 + +* 兼容`LocalDate` [Issue #2908](https://github.com/alibaba/easyexcel/issues/2908) +* 优化大文件内存存储,减少内存占用 [Issue #2657](https://github.com/alibaba/easyexcel/issues/2657) +* 在临时文件被删除的情况下能正常的读取 [Issue #2693](https://github.com/alibaba/easyexcel/issues/2693) +* 分页读取监听器支持自定义分页条数 [Issue #2383](https://github.com/alibaba/easyexcel/issues/2383) + +# 3.2.0 + +* 修复部分xlsx读取日期可能相差1秒的bug [Issue #1956](https://github.com/alibaba/easyexcel/issues/1956) +* 修复部分数据精度和excel不匹配的bug [Issue #2805](https://github.com/alibaba/easyexcel/issues/2805) +* 不创建对象的读支持读取原始的数据类型 + +# 3.1.5 + +* 提高xlsx读取兼容性:兼用ns2开头的标签 + +# 3.1.4 + +* 提高xlsx读取兼容性:在存在第一行很多空 +* 列的情况下,忽略空列 + +# 3.1.3 + +* 提高xlsx兼容性:兼容`sharedStrings.xml` 存在 `x:t`标签的情况 + +# 3.1.2 + +* 修复长时间运行会产生大对象的bug [Issue #2725](https://github.com/alibaba/easyexcel/issues/2725) + +# 3.1.1 + +* 修复部分xlsx无法读取超链接的bug + +# 3.1.0 + +* 支持jdk17,去除cglib&asm依赖,改成重新拷贝一份 [Issue #2240](https://github.com/alibaba/easyexcel/issues/2240) +* 升级ehcache 到 3.9.9 ,为了兼容jdk17 +* 在有样式没有数据的情况下也算空行 [Issue #2294](https://github.com/alibaba/easyexcel/issues/2294) +* 修复无法根据文件流判断csv的bug [Issue #2297](https://github.com/alibaba/easyexcel/issues/2297) +* 修复CSV不关闭流的bug [Issue #2309](https://github.com/alibaba/easyexcel/issues/2309) +* 修复`filed`拼接错误 [Issue #2390](https://github.com/alibaba/easyexcel/issues/2390) +* 修复`BigIntegerStringConverter`无效的bug [Issue #2325](https://github.com/alibaba/easyexcel/issues/2325) +* 修复03版本无法读取到公式格式的bug [Issue #2444](https://github.com/alibaba/easyexcel/issues/2444) +* 提高类型转换兼容性 [Issue #2443](https://github.com/alibaba/easyexcel/issues/2443) +* 捕获`setFeature`异常不影响主流程 [Issue #2054](https://github.com/alibaba/easyexcel/issues/2054) +* 增加部分`xls`容错 [Issue #2236](https://github.com/alibaba/easyexcel/issues/2236) +* 修复读取csv文件时`autoTrim`配置失效 [Issue #2227](https://github.com/alibaba/easyexcel/issues/2227) +* csv支持设置读写编码 [Issue #2404](https://github.com/alibaba/easyexcel/issues/2404) +* 读写支持`try-with-resources`语法糖 + +# 3.0.5 + +* 修复`ReadListener` 转换异常不抛出的问题 + +# 3.0.4 + +* 调整读写默认大小,防止大批量写的时候可能会full gc +* `fill`的情况新增 `afterRowDispose`事件 + +# 3.0.3 + +* 修复`HeadStyle`无效的bug + +# 3.0.2 + +* 大幅提升读写性能 +* 修复列宽注解没用的bug [Issue #2151](https://github.com/alibaba/easyexcel/issues/2151) +* 修复`CellData`接收失败的的bug [Issue #2147](https://github.com/alibaba/easyexcel/issues/2147) + +# 3.0.1 + +* 升级到正式版 +* 修复填充样式可能丢失的问题 [Issue #2124](https://github.com/alibaba/easyexcel/issues/2124) +* 修复填充数据为空 可能NPE的bug +* 修复填充样式可能不生效bug +* 修复样式可能超过最大限制的bug +* 修复写入过慢的bug + +# 3.0.0-beta3 + +* 修复导出浮点型数据可能精度异常的bug + +# 3.0.0-beta2 + +* 优化写入样式 + +# 3.0.0-beta1 + +* 升级jdk8 不再支持jdk6 jdk7 +* 升级poi 到 4.1.2 +* 升级cglib 到 3.3.0 +* 升级ehcache 到 3.8.1 +* 支持非驼峰的字段读写 +* 支持csv的读&写 +* 修复`CellData`可能不返回行列号 [Issue #1832](https://github.com/alibaba/easyexcel/issues/1832) +* 优化读取性能 +* 修复部分情况下不抛出异常 +* 07版在导出的时候会导出 行数 [Issue #1282](https://github.com/alibaba/easyexcel/issues/1282) +* 修复没有样式的情况下空指针异常 [Issue #1738](https://github.com/alibaba/easyexcel/issues/1738) +* 修改异常抛出逻辑 [Issue #1618](https://github.com/alibaba/easyexcel/issues/1618) +* 兼容一些非官方excel的情况 [Issue #1527](https://github.com/alibaba/easyexcel/issues/1527) +* 修改读的关闭流无效 [Issue #1840](https://github.com/alibaba/easyexcel/issues/1840) +* 写入支持Collection [Issue #1834](https://github.com/alibaba/easyexcel/issues/1834) +* `Converter`支持null转换 [Issue #1776](https://github.com/alibaba/easyexcel/issues/1776) +* cglib 新增命名策略,防止和`spring`的冲突 [Issue #2064](https://github.com/alibaba/easyexcel/issues/2064) +* 修改填充可能填充错误的bug [Issue #2035](https://github.com/alibaba/easyexcel/issues/2035) +* 修复无对象读 返回map的size可能会头的size不一致 [Issue #2014](https://github.com/alibaba/easyexcel/issues/2014) +* 修复合并头可能异常的bug [Issue #1662](https://github.com/alibaba/easyexcel/issues/1662) +* 修复填充调用横向样式策略报错 [Issue #1651](https://github.com/alibaba/easyexcel/issues/1651) +* 修复不自动行高的问题 [Issue #1869](https://github.com/alibaba/easyexcel/issues/1869) +* 新增头的非空校验 [Issue #1765](https://github.com/alibaba/easyexcel/issues/1765) +* 修复某些特殊的excel读取失败的问题 [Issue #1595](https://github.com/alibaba/easyexcel/issues/1595) +* 修复不创建对象写入数据异常 [Issue #1702](https://github.com/alibaba/easyexcel/issues/1702) +* 修复头和数据对象不一致会覆盖的问题 [Issue #1870](https://github.com/alibaba/easyexcel/issues/1870) +* 修复忽略字段后可能排序不一致的问题 +* 修改填充时,无法使用生成的模板 [Issue #1552](https://github.com/alibaba/easyexcel/issues/1552) +* 修改填充可以不自动继承样式 [Issue #1710](https://github.com/alibaba/easyexcel/issues/1710) +* 修复填充数据不能为空的问题 [Issue #1703](https://github.com/alibaba/easyexcel/issues/1703) +* 新增部分jdk8特性 + +# 2.2.11 + +* 修复有些xlsx解析失败的bug [Issue #1595](https://github.com/alibaba/easyexcel/issues/1595) + +# 2.2.10 + +* 修复读取的时候用string接收数字 可能四舍五入不一致的bug + +# 2.2.9 + +* 修复读取的时候用string接收数字 可能四舍五入不一致的bug + +# 2.2.8 + +* 兼容07在特殊的excel的情况下,读取数据异常 + +# 2.2.7 + +* 修改07在特殊情况下用`String`接收数字会丢小数位的bug + +# 2.2.6 + +* 修改跳着读取03版本空指针bug + +# 2.2.5 + +* `ExcelProperty`新增`order` 用于排序 +* 修复导出指定`index`会导致空行的bug + +# 2.2.4 + +* 撤销删除`AbstractMergeStrategy` +* 修改默认用String读取数字不使用科学计数法 通过`useScientificFormat`修改 +* 修复07版仅有样式的空行 默认不忽略的bug +* 写入`sheet`不设置`index`和`name`默认不为0的问题 +* 修复多个`sheet`不按照顺序写入 会乱序的bug [Issue #1332](https://github.com/alibaba/easyexcel/issues/1332) +* 修改head是List时,内容单元格的样式不生效 [Issue #1339](https://github.com/alibaba/easyexcel/issues/1339) +* 修复xls仅公式行 不读取的bug [Issue #1324](https://github.com/alibaba/easyexcel/issues/1324) +* 修复xls直接读取第2页 `NPE` 的bug [Issue #1280](https://github.com/alibaba/easyexcel/issues/1280) +* 修复填充的时候,最后一行中间有空行会创建失败的bug +* 修复`includeColumnIndexes`不包含第列 + 会无法导出数据的bug [Issue #1346](https://github.com/alibaba/easyexcel/issues/1346) +* 修复`@NumberFormat`注解转换double时可能会丢失精度 [Issue #1306](https://github.com/alibaba/easyexcel/issues/1306) + +# 2.2.3 + +* 修改填充数据空数据的bug [Issue #1274](https://github.com/alibaba/easyexcel/issues/1274) +* 回退自定义转换器入参为空 + +# 2.2.2 + +* 修改`sheet`事件未调用的bug +* 修复复杂表头不是`index=0`开始 合并异常的bug [Issue #1322](https://github.com/alibaba/easyexcel/issues/1322) + +# 2.2.1 + +* 发布正式版 +* 修复第一行为空不会调用`invokeHeadMap`的bug [Issue #993](https://github.com/alibaba/easyexcel/issues/993) +* + +当类的属性没有按照ExcelProperty的属性index顺序排序的时候,写数据出现错乱 [Issue #1046](https://github.com/alibaba/easyexcel/issues/1046) + +* 新增支持自定义转换器 入参可以为空 实现`NullableObjectConverter` + 即可 [Issue #1084](https://github.com/alibaba/easyexcel/issues/1084) +* 修复xls丢失结束标记的情况下 会漏读最后一行 +* 修复填充的时候 多次`forceNewRow` 空指针的bug [Issue #1201](https://github.com/alibaba/easyexcel/issues/1201) +* 修复`table`、`sheet`中创建的拦截器不执行`workbook` + 事件的bug [Issue #1202](https://github.com/alibaba/easyexcel/issues/1202) + +# 2.2.0-beta2 + +* 修复最长匹配策略不同表格会有影响的bug [Issue #1010](https://github.com/alibaba/easyexcel/issues/1010) +* `LinkedList`写入的性能问题 #1121 +* 修复在某些情况下可能出现不必要的`warn`日志 + +# 2.2.0-beta1 + +* 重写主流程,代码更加优雅 +* 修复用String接收日期、数字和excel显示不一致的bug(不是完美修复,但是大部分情况已经兼容) +* 降低Ehcache版本 3.7.1(jkd7) -> 3.4.0(jdk6) +* 修复xls 用Map接收时多次接收会是同一个对象的bug +* 修复浮点型数据导入到excel 会丢失精度的bug +* 新增支持读取批注、超链接、合并单元格 +* 如果是`RuntimeException`则不再封装对象 +* 新增`CellData`可以获取行列号 +* 新增样式注解 +* 新增合并单元格注解 +* 提升合并策略效率 +* 兼容部分比较特殊的excel +* 同时传入了`List>`和`class`的head,会通过index去匹配注解 +* 修复读取转换器的并发问题 +* 填充支持多个List对象 + +# 2.1.7 + +* 修复使用1+版本的写法,第1条开始读修改为第0条开始读 + +# 2.1.6 + +* 修复写入只有`sheetName`会抛异常 + +# 2.1.5 + +* 修复部分xlsx没有行号读取异常 +* 填充时候支持根据`sheetName`定位`sheet` + +# 2.1.4 + +* 新增参数`useDefaultListener` 可以排除默认对象转换 + +# 2.1.3 + +* 每个java进程单独创建一个缓存目录 [Issue #813](https://github.com/alibaba/easyexcel/issues/813) +* 统一修改合并为unsafe,提高大量数据导出的合并的效率 +* 修改merge返回参数`relativeRowIndex`为`Integer` +* 新增参数`automaticMergeHead` 可以设置不自动合并头 [Issue #822](https://github.com/alibaba/easyexcel/issues/822) +* 新增参数`xlsxSAXParserFactoryName` 可以指定`SAXParserFactory` +* 修复合并策略 空指针的问题 +* `SimpleColumnWidthStyleStrategy` 新增 参数`columnIndex` [Issue #806](https://github.com/alibaba/easyexcel/issues/806) + +# 2.1.2 + +* 修复强制创建新行填充,只有一行数据会未填充的bug + +# 2.1.1 + +* 发布正式版 +* 修改map返回为LinkedHashMap +* 修改同步读取返回对象支持泛型 +* 修复03版不能直接读取第二个sheet的bug [Issue #772](https://github.com/alibaba/easyexcel/issues/772) +* 新增支持图片导出用URL [Issue #774](https://github.com/alibaba/easyexcel/issues/774) +* 加入多次关闭判断,防止多次关闭异常 +* 加入根据模板自动识别导出的excel类型 +* 修改默认失败后,不再往文件流写入数据。通过参数`writeExcelOnException` 参数设置异常了也要写入前面的数据。 +* 循环合并策略支持一次性合并多列 +* `ExcelDataConvertException`返回新增具体报错的数据 +* 加入解析class缓存 +* 修复填充的时候行高不复制的Bug [Issue #780](https://github.com/alibaba/easyexcel/issues/780) +* 修复03版无法获取大概总行数的bug + +# 2.1.0-beta4 + +* 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) +* 修改afterRowDispose错误 [Issue #751](https://github.com/alibaba/easyexcel/issues/751) +* 修复多个头的情况下会读取数据为空 + +# 2.1.0-beta3 + +* 支持强行指定在内存处理,以支持备注、RichTextString等的写入 +* 修复关闭流失败,可能会不删除临时文件的问题 +* 支持根据参数自定义导出列 +* 修改最长匹配策略的最大长度 [Issue #734](https://github.com/alibaba/easyexcel/issues/734) +* 修复策略头未生效的bug [Issue #735](https://github.com/alibaba/easyexcel/issues/735) +* 修复填充的时候有数字会异常 + +# 2.1.0-beta2 + +* 修改模板通过流创建报错的bug +* 修复空数据未替换掉的bug +* 修复空模板会空一行的bug + +# 2.1.0-beta1 + +* 新增支持导入、导出支持公式 +* 新增支持读取单元格类型、写入指定单元格类型 +* 支持通过模板填充数据 +* 新增写支持 禁用头样式 `useDefaultStyle` +* 用map读取数据 空的单元格也会有个 null的数据 +* 转换报错 能获取到对应的行号和列号 +* 优化读取全部sheet方案 +* 新增注解`ExcelIgnoreUnannotated` 支持忽略未加`ExcelProperty`注解的字段 +* 支持导出加密 [Issue #361](https://github.com/alibaba/easyexcel/issues/361) +* 支持导入加密 [Issue #295](https://github.com/alibaba/easyexcel/issues/295) + +# 2.0.5 + +* 优化07版超大文件读取方案 +* 支持自己设置超大文件读取参数 +* 读取xlsx会改变修改时间的bug [Issue #574](https://github.com/alibaba/easyexcel/issues/574) +* 默认读取忽略空行 根据参数ignoreEmptyRow参数设置 + +# 2.0.4 + +* 修复07版整个excel仅存在数字时会出现的NPE +* 修复03版 用String接收电话会出现科学计数法的问题 + +# 2.0.3 + +* 修复重大bug 在07版读取文件的时候 小概率导致数字部分丢失 + +# 2.0.2 + +* 修复xls无法获取sheetList的bug [Issue #621](https://github.com/alibaba/easyexcel/issues/621) +* 修复监听器转换异常会重复提示的bug + +# 2.0.1 + +* 降级poi为3.17 兼容jdk6 + +# 2.0.0 + +* 修复当cell为空可能会抛出空指针的bug +* 修复电话等长数字可能出现科学计数法的问题 [Issue #583](https://github.com/alibaba/easyexcel/issues/583) +* 升级为正式版 + +# 2.0.0-beta6 + +* 修复空行读取空指针异常 +* 修复写入指定头为List>,但是数据用List导致的空指针 + +# 2.0.0-beta5 + +* 修复在读取值的时候读取了额外值导致数据转换异常 + +# 2.0.0-beta4 + +* 修改在传入List>判断行数错误 [Issue #526](https://github.com/alibaba/easyexcel/issues/526) +* 修复在mac 2016 2017导出的excel 可能存在多余字段的问题 +* 修复03版 读取无法指定sheet的问题 [Issue #533](https://github.com/alibaba/easyexcel/issues/533) + +# 2.0.0-beta3 + +* 导出完成移除临时目录 [Issue #386](https://github.com/alibaba/easyexcel/issues/386) +* 新增读取返回头数据 + +# 2.0.0-beta2 + +* 加速gc回收 [Issue #511](https://github.com/alibaba/easyexcel/issues/511) +* 修改空字符串读取可能读取上个字段的数据的bug +* 修改换行数据无法读取的bug [Issue #521](https://github.com/alibaba/easyexcel/issues/521) +* 修复在空字符串的时候 格式转换异常 [Issue #520](https://github.com/alibaba/easyexcel/issues/520) + +# 2.0.0-beta1 + +* 优化读写逻辑 +* 优化读写对外接口 +* 加入转换器,方便格式转换 +* 极大优化读大文件的内存和效率 +* sheetNo 改成0开始 +* 读支持指定列名 +* 升级poi 到4.0.1 + +# 1.2.4 + +修复read()方法存在的bug + +# 1.2.1 + +修复POI在大并发情况下创建临时目录失败的bug + +# 1.0.9 + +修复excel超过16列被覆盖的问题,修复数据只有一行时候无法透传的bug。 + +# 1.0.8 + +如果整行excel数据全部为空,则不解析返回。完善多sheet的解析。 + +# 1.0.6 + +增加@ExcelColumnNum,修复字符串前后空白,增加过滤功能。 + +# 1.0.5 + +优化类型转换的性能。 + +# 1.0.4 + +修复日期类型转换时候数字问题。基础模型支持字段类型int,long,double,boolean,date,string + +# 1.0.3 + +修复无@ExcelProperty标注的多余字段时候报错。 + +# 1.0.2 + +修复拿到一行数据后,存到list中,但最后处理时候变为空的bug。 + +# 1.0.1 + +完善测试用例,防止歧义,模型字段映射不上时候有抛异常,改为提醒。 \ No newline at end of file