diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md new file mode 100644 index 00000000..8d7f460f --- /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 index 4ea8cee6..7ef28dd1 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -7,12 +7,12 @@ assignees: zhuangjiaju --- -**建议先去看文档** -[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq) -**触发场景描述** +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 触发场景描述 -**触发Bug的代码** +# 触发Bug的代码 ```java 这里写代码 ``` -**提示的异常或者没有达到的效果** +# 提示的异常或者没有达到的效果 diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 51cea2b3..bd234cf8 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -7,12 +7,12 @@ assignees: '' --- -**建议先去看文档** -[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq) -**异常代码** +# 建议先去看文档 +[快速开始](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 index 1aeecd86..41884ceb 100644 --- a/.github/ISSUE_TEMPLATE/suggest.md +++ b/.github/ISSUE_TEMPLATE/suggest.md @@ -7,6 +7,6 @@ assignees: '' --- -**建议先去看文档** -[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq) -**建议描述** +# 建议先去看文档 +[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/) +# 建议描述 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76e5b481..cc68ecf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,18 +30,19 @@ jobs: name: Test JDK ${{ matrix.java }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@main - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@main with: java-version: ${{ matrix.java }} distribution: ${{ matrix.distribution }} - - name: Cache Maven - uses: actions/cache@v2 + - name: Cache local Maven repository + uses: actions/cache@v3 with: - path: ~/.m2 - key: m2 - restore-keys: m2 + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..3051a057 --- /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@v3 + 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 00000000..cc88d581 --- /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, pull_request] + +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/README.md b/README.md index 8eafb40f..9586e7b3 100644 --- a/README.md +++ b/README.md @@ -3,101 +3,125 @@ 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-%E6%9F%A5%E7%9C%8B%E8%B4%A1%E7%8C%AE%E6%8E%92%E8%A1%8C%E6%A6%9C-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=easyexcel) +# JAVA解析Excel工具 + +Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。 +easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便 + +# 网站 +* 官方网站:[https://easyexcel.opensource.alibaba.com/](https://easyexcel.opensource.alibaba.com/) +* github地址:[https://github.com/alibaba/easyexcel](https://github.com/alibaba/easyexcel) +* gitee地址:[https://gitee.com/easyexcel/easyexcel](https://gitee.com/easyexcel/easyexcel) + +# 64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本) + +当然还有[极速模式](https://easyexcel.opensource.alibaba.com/qa/read#%E5%BC%80%E5%90%AF%E6%80%A5%E9%80%9F%E6%A8%A1%E5%BC%8F) +能更快,但是内存占用会在100M多一点 +![img](img/readme/large.png) + +# 最新版本 + +```xml + + + com.alibaba + easyexcel + 3.2.0 + +``` + +# 帮忙点个⭐Star + +开源不易,如果觉得EasyExcel对您的工作还是有帮助的话,请帮忙在github star +的右上角点个⭐Star,您的支持是使EasyExcel变得更好最大的动力。 + +# 人员招聘 + +由于工作较忙,需要招聘一些同学加入`EasyExcel`开源,一起跟着`EasyExcel`成长。 + +## 你将获得 + +* 认识一帮热爱开源的小伙伴 +* 你写的代码可以被无数人看到,提升自我编码能力 +* 可能会有一定的物质奖励(在和公司申请,不一定能过) + +## 你的工作 + +* 群&`issue`答疑 +* 做一些代码的`PR`合并去修复bug +* 讨论`EasyExcel`规划 + +## 招聘要求 + +* 3年以上Java编程经验,基础扎实 +* 对技术比较热爱,并且喜欢阅读源码 +* 自驱力强,能主动的研究一些问题 +* 需要持之以恒,开源需要长期维护 + +## 联系方式 + +直接加钉钉群,联系是仪即可 + +# 如何获取帮助 + +## 优先建议自己通过文档来解决问题 + +* [快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) +* [常见问题](https://easyexcel.opensource.alibaba.com/docs/qa/) +* [API](https://easyexcel.opensource.alibaba.com/docs/current/api/) + +## 其次建议通过`issues`来解决解决问题 + +可以尝试在以下2个链接搜索问题,如果不存在可以尝试创建`issue`。 + +* 去 [github](https://github.com/alibaba/easyexcel/issues) 搜索`issues` +* 去 [gitee](https://gitee.com/easyexcel/easyexcel/issues) 搜索`issues` + +通过 `issues` 解决问题,可以给后面遇到相同问题的同学查看,所以比较推荐这种方式。 +不管`github`、`gitee`都会定期有人回答您的问题,比较紧急可以在提完`issue`以后在钉钉群艾特群主并发送`issue`地址帮忙解决。 +`QQ` 公司不让用,有时候也会去看,但是核心肯定还是在钉钉。 + +## 也可以加入钉钉&QQ群来解决问题 + +加入钉钉或QQ群,看完公告可以获得帮助 。 +比较推荐钉钉群,`QQ` 公司不让用,当然QQ群也会有热心网友帮忙解决。 [QQ1群(已满): 662022184](https://jq.qq.com/?_wv=1027&k=1T21jJxh) [QQ2群(已满): 1097936804](https://jq.qq.com/?_wv=1027&k=j5zEy6Xl) [QQ3群(已满): 453928496](https://qm.qq.com/cgi-bin/qm/qr?k=e2ULsA5A0GldhV2CXJ8sIbAyu9I6qqs7&jump_from=webapi) -[QQ4群: 496594404](https://qm.qq.com/cgi-bin/qm/qr?k=e_aVG1Q7gi0PJUBkbrUGAgbeO3kUEInK&jump_from=webapi) +[QQ4群(已满): 496594404](https://qm.qq.com/cgi-bin/qm/qr?k=e_aVG1Q7gi0PJUBkbrUGAgbeO3kUEInK&jump_from=webapi) +[QQ5群(已满): 451925680](https://jq.qq.com/?_wv=1027&k=6VHhvxyf) +[QQ6群: 784741035](https://jq.qq.com/?_wv=1027&k=BbLBIo9P) [钉钉1群(已满): 21960511](https://qr.dingtalk.com/action/joingroup?code=v1,k1,cchz6k12ci9B08NNqhNRFGXocNVHrZtW0kaOtTKg/Rk=&_dt_no_comment=1&origin=11) [钉钉2群(已满): 32796397](https://qr.dingtalk.com/action/joingroup?code=v1,k1,jyU9GtEuNU5S0QTyklqYcYJ8qDZtUuTPMM7uPZTS8Hs=&_dt_no_comment=1&origin=11) [钉钉3群(已满): 33797247](https://qr.dingtalk.com/action/joingroup?code=v1,k1,3UGlEScTGQaHpW2cIRo+gkxJ9EVZ5fz26M6nW3uFP30=&_dt_no_comment=1&origin=11) [钉钉4群(已满): 33491624](https://qr.dingtalk.com/action/joingroup?code=v1,k1,V14Pb65Too70rQkEaJ9ohb6lZBZbtp6jIL/q9EWh9vA=&_dt_no_comment=1&origin=11) [钉钉5群(已满): 32134498](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingb9fa1325d9dccc3ecac589edd02f1650&5233a=71a83&cbdbhh=qwertyuiop) [钉钉6群(已满): 34707941](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingcf68008a1d443ac012d5427bdb061b7a&6ae36c3d-0c80-4=22398493-6c2a-4&cbdbhh=qwertyuiop) -[钉钉7群: 35235427](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=ding532b9018c06c7fc8660273c4b78e6440&167fb=ed003&cbdbhh=qwertyuiop) -[官方网站: https://yuque.com/easyexcel](https://www.yuque.com/easyexcel/doc/easyexcel) - -[常见问题](https://www.yuque.com/easyexcel/faq) -#### 因为公司不方便用QQ,所以建议加钉钉群 +[钉钉7群(已满): 35235427](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=ding532b9018c06c7fc8660273c4b78e6440&167fb=ed003&cbdbhh=qwertyuiop) +[钉钉8群(已满): 44752220](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingea96808beee421693fd4ba7542d6e5da&0380092a-fa46=a6a40905-7951&cbdbhh=qwertyuiop) +[钉钉9群(已满): 11045002277](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dinge338d2215891c0459c13cd6b2cb108a6&6972d=b92f9&cbdbhh=qwertyuiop) +[钉钉10群: 27360019755](https://qr.dingtalk.com/action/joingroup?code=v1,k1,v25LHn2liWmrWUKlkhIzOTcK7s7onp/sZP8mO5oIYSs=&_dt_no_comment=1&origin=11) -# JAVA解析Excel工具EasyExcel -Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便 +# 维护者 -## 64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本) -当然还有极速模式能更快,但是内存占用会在100M多一点 -![img](img/readme/large.png) - -## 关于版本选择 -如果项目中没有使用过poi,且jdk版本在8-17之间,直接使用最新版本,别犹豫。以下表格适用于不满足以上2个情况的。 - -| 版本 | poi依赖版本 (支持范围) | jdk版本支持范围 | 备注 | -|--------------------|-----------------------|--------------|---------------------------------------------| -| 3.1.0+ | 4.1.2 (4.1.2 - 5.2.2) | jkd8 - jdk17 | 推荐使用,会更新的版本 | -| 3.0.0-beta1 - 3.0.5 | 4.1.2 (4.1.2 - 5.2.2) | jkd8 - jdk11 | 不推荐项目新引入此版本,除非超级严重bug,否则不再更新 | -| 2.0.0-beta1-2.2.11 | 3.17 (3.17 - 4.1.2) | jdk6 - jdk11 | 不推荐项目新引入此版本,除非是jdk6否则不推荐使用,除非超级严重bug,否则不再更新 | -| 1+版本 | 3.17 (3.17 - 4.1.2) | jdk6 - jdk11 | 不推荐项目新引入此版本,超级严重bug,也不再更新 | - -注意: 3+版本的的easyexcel,使用poi 5+版本时,需要手动排除:poi-ooxml-schemas,例如: -```xml - - com.alibaba - easyexcel - 3.1.0 - - - poi-ooxml-schemas - org.apache.poi - - - -``` +姬朋飞(玉霄)、庄家钜、怀宇 -### 关于版本升级 -* 不建议跨大版本升级 尤其跨2个大版本 -* 2+ 升级到 3+ 一些不兼容的地方 - * 使用了自定义拦截器去修改样式的会出问题(不会编译报错) - * 读的时候`invoke`里面抛出异常,不会再额外封装一层`ExcelAnalysisException` (不会编译报错) - * 样式等注解涉及到 `boolean` or 一些枚举 值的 有变动,新增默认值(会编译报错,注解改就行) -* 大版本升级后建议相关内容重新测试下 +# 快速开始 -### 最新版本 -```xml - - com.alibaba - easyexcel - 3.1.0 - -``` +## 读Excel -### easyexcel人员招募 -由于工作较忙,有意愿做开源的同学可以报名,主要负责群里回答&issue处理,当然也可以做一些PR. -由于开源没有任何物质回报,然后现在的维护同学也是课余时间维护的,所以想加入的同学需要持之以恒,而不是一时兴起. -要求如下: -* 有一定java编码能力 & 良好的编码习惯 -* 了解easyexcel 读&写的原理 -* 热爱开源项目 -* 能长期坚持的去做 -* 相对工作没那么忙 - -## 相关文档 -* [快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) -* [关于软件](/abouteasyexcel.md) -* [更新记事](/update.md) -* [贡献代码](https://www.yuque.com/easyexcel/doc/contribute) - -## 维护者 -姬朋飞(玉霄)、庄家钜、怀宇 -## 快速开始 -### 读Excel -DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) +demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) +详细文档地址:[https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read) ```java /** - * 最简单的读 - *

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

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

3. 直接读即可 - */ + * 最简单的读 + *

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

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

3. 直接读即可 + */ @Test public void simpleRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; @@ -106,8 +130,11 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t } ``` -### 写Excel -DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java) +## 写Excel + +demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java) +详细文档地址:[https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write) + ```java /** * 最简单的写 @@ -116,36 +143,38 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t */ @Test public void simpleWrite() { - String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + String fileName=TestFileUtil.getPath()+"write"+System.currentTimeMillis()+".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 - EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); + EasyExcel.write(fileName,DemoData.class).sheet("模板").doWrite(data()); } ``` -### web上传、下载 -DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) +## web上传、下载 + +demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) + ```java - /** - * 文件下载(失败了会返回一个有部分数据的Excel) - *

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

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

- * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 - */ + /** + * 文件下载(失败了会返回一个有部分数据的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()); + 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()); } - + /** * 文件上传 *

1. 创建excel对应的实体对象 参照{@link UploadData} @@ -154,10 +183,8 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t */ @PostMapping("upload") @ResponseBody - public String upload(MultipartFile file) throws IOException { - EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); - return "success"; + public String upload(MultipartFile file)throws IOException{ + EasyExcel.read(file.getInputStream(),UploadData.class,new UploadDataListener(uploadDAO)).sheet().doRead(); + return"success"; } ``` -### 联系我们 -有问题阿里同事可以通过钉钉找到我,阿里外同学可以通过git留言。其他技术非技术相关的也欢迎一起探讨。 diff --git a/README_EN.md b/README_EN.md index ae787bbb..619f4161 100644 --- a/README_EN.md +++ b/README_EN.md @@ -3,6 +3,7 @@ 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) diff --git a/abouteasyexcel.md b/abouteasyexcel.md deleted file mode 100644 index 54a4e1fd..00000000 --- a/abouteasyexcel.md +++ /dev/null @@ -1,53 +0,0 @@ -# easyexcel要去解决的问题 - -## Excel读写时候内存溢出 - -虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。 - -## 其他开源框架使用复杂 - -对POI有过深入了解的估计才知道原来POI还有SAX模式。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。 - -## 其他开源框架存在一些BUG修复不及时 - -由于我们的系统大多数都是大并发的情况下运行的,在大并发情况下,我们会发现poi存在一些bug,如果让POI团队修复估计遥遥无期了。所以我们在easyexcel对这些bug做了规避。 -如下一段报错就是在大并发情况下poi抛的一个异常。 -``` -Caused by: java.io.IOException: Could not create temporary directory '/home/admin/dio2o/.default/temp/poifiles' - at org.apache.poi.util.DefaultTempFileCreationStrategy.createTempDirectory(DefaultTempFileCreationStrategy.java:93) ~[poi-3.15.jar:3.15] - at org.apache.poi.util.DefaultTempFileCreationStrategy.createPOIFilesDirectory(DefaultTempFileCreationStrategy.java:82) ~[poi-3.15.jar:3.15] -``` -报错地方poi源码如下 -``` - private void createTempDirectory(File directory) throws IOException { - if (!(directory.exists() || directory.mkdirs()) || !directory.isDirectory()) { - throw new IOException("Could not create temporary directory '" + directory + "'"); - } - } -``` -仔细看代码容易明白如果在并发情况下,如果2个线程同时判断directory.exists()都 为false,但执行directory.mkdirs()如果一些线程优先执行完,另外一个线程就会返回false。最终 throw new IOException("Could not create temporary directory '" + directory + "'")。针对这个问题easyexcel在写文件时候首先创建了该临时目录,避免poi在并发创建时候引起不该有的报错。 - -## Excel格式分析格式分析 - -- xls是Microsoft Excel2007前excel的文件存储格式,实现原理是基于微软的ole db是微软com组件的一种实现,本质上也是一个微型数据库,由于微软的东西很多不开源,另外也已经被淘汰,了解它的细节意义不大,底层的编程都是基于微软的com组件去开发的。 -- xlsx是Microsoft Excel2007后excel的文件存储格式,实现是基于openXml和zip技术。这种存储简单,安全传输方便,同时处理数据也变的简单。 -- csv 我们可以理解为纯文本文件,可以被excel打开。他的格式非常简单,解析起来和解析文本文件一样。 - -## 核心原理 - -写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。 -读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。 -分析清楚POI后要解决OOM有3个关键。 - -### 1、文件解压文件读取通过文件形式 - -![屏幕快照 2018-01-22 上午8.52.08.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/e3a3500014c95f7118d8c200a51acab4.png) - -### 2、避免将全部全部数据一次加载到内存 - -采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。 -![基础模板1 (2).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/82bb195ac62532963b2364d2e4da23e5.png) - -### 3、抛弃不重要的数据 - -Excel解析时候会包含样式,字体,宽度等数据,但这些数据是我们不关心的,如果将这部分数据抛弃可以大大降低内存使用。Excel中数据如下Style占了相当大的空间。 \ No newline at end of file diff --git a/easyexcel-core/pom.xml b/easyexcel-core/pom.xml index 4f03d716..d6e58002 100644 --- a/easyexcel-core/pom.xml +++ b/easyexcel-core/pom.xml @@ -13,6 +13,7 @@ jar easyexcel-core + easyexcel-core diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java b/easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java index ef9b3ac3..80c40851 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java @@ -14,25 +14,10 @@ import com.alibaba.excel.write.builder.ExcelWriterTableBuilder; /** * Reader and writer factory class * - *

Quick start

- *

Read

- *

Sample1

- * - *

Sample2

- * - *

Write

- * - *

Sample1

- * - *

Sample2

- * - * - * * @author jipengfei */ public class EasyExcelFactory { - /** * Build excel the write * @@ -45,8 +30,7 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param file - * File to write + * @param file File to write * @return Excel writer builder */ public static ExcelWriterBuilder write(File file) { @@ -56,10 +40,8 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param file - * File to write - * @param head - * Annotate the class for configuration information + * @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) { @@ -74,8 +56,7 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param pathName - * File path to write + * @param pathName File path to write * @return Excel writer builder */ public static ExcelWriterBuilder write(String pathName) { @@ -85,10 +66,8 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param pathName - * File path to write - * @param head - * Annotate the class for configuration information + * @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) { @@ -103,8 +82,7 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param outputStream - * Output stream to write + * @param outputStream Output stream to write * @return Excel writer builder */ public static ExcelWriterBuilder write(OutputStream outputStream) { @@ -114,10 +92,8 @@ public class EasyExcelFactory { /** * Build excel the write * - * @param outputStream - * Output stream to write - * @param head - * Annotate the class for configuration information. + * @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) { @@ -141,8 +117,7 @@ public class EasyExcelFactory { /** * Build excel the writerSheet * - * @param sheetNo - * Index of sheet,0 base. + * @param sheetNo Index of sheet,0 base. * @return Excel sheet writer builder. */ public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo) { @@ -152,8 +127,7 @@ public class EasyExcelFactory { /** * Build excel the 'writerSheet' * - * @param sheetName - * The name of sheet. + * @param sheetName The name of sheet. * @return Excel sheet writer builder. */ public static ExcelWriterSheetBuilder writerSheet(String sheetName) { @@ -163,10 +137,8 @@ public class EasyExcelFactory { /** * Build excel the 'writerSheet' * - * @param sheetNo - * Index of sheet,0 base. - * @param sheetName - * The name of sheet. + * @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) { @@ -192,8 +164,7 @@ public class EasyExcelFactory { /** * Build excel the 'writerTable' * - * @param tableNo - * Index of table,0 base. + * @param tableNo Index of table,0 base. * @return Excel table writer builder. */ public static ExcelWriterTableBuilder writerTable(Integer tableNo) { @@ -216,8 +187,7 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param file - * File to read. + * @param file File to read. * @return Excel reader builder. */ public static ExcelReaderBuilder read(File file) { @@ -227,10 +197,8 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param file - * File to read. - * @param readListener - * Read listener. + * @param file File to read. + * @param readListener Read listener. * @return Excel reader builder. */ public static ExcelReaderBuilder read(File file, ReadListener readListener) { @@ -240,12 +208,9 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param file - * File to read. - * @param head - * Annotate the class for configuration information. - * @param readListener - * Read listener. + * @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) { @@ -263,8 +228,7 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param pathName - * File path to read. + * @param pathName File path to read. * @return Excel reader builder. */ public static ExcelReaderBuilder read(String pathName) { @@ -274,10 +238,8 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param pathName - * File path to read. - * @param readListener - * Read listener. + * @param pathName File path to read. + * @param readListener Read listener. * @return Excel reader builder. */ public static ExcelReaderBuilder read(String pathName, ReadListener readListener) { @@ -287,12 +249,9 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param pathName - * File path to read. - * @param head - * Annotate the class for configuration information. - * @param readListener - * Read listener. + * @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) { @@ -310,8 +269,7 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param inputStream - * Input stream to read. + * @param inputStream Input stream to read. * @return Excel reader builder. */ public static ExcelReaderBuilder read(InputStream inputStream) { @@ -321,10 +279,8 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param inputStream - * Input stream to read. - * @param readListener - * Read listener. + * @param inputStream Input stream to read. + * @param readListener Read listener. * @return Excel reader builder. */ public static ExcelReaderBuilder read(InputStream inputStream, ReadListener readListener) { @@ -334,12 +290,9 @@ public class EasyExcelFactory { /** * Build excel the read * - * @param inputStream - * Input stream to read. - * @param head - * Annotate the class for configuration information. - * @param readListener - * Read listener. + * @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) { @@ -366,8 +319,7 @@ public class EasyExcelFactory { /** * Build excel the 'readSheet' * - * @param sheetNo - * Index of sheet,0 base. + * @param sheetNo Index of sheet,0 base. * @return Excel sheet reader builder. */ public static ExcelReaderSheetBuilder readSheet(Integer sheetNo) { @@ -377,8 +329,7 @@ public class EasyExcelFactory { /** * Build excel the 'readSheet' * - * @param sheetName - * The name of sheet. + * @param sheetName The name of sheet. * @return Excel sheet reader builder. */ public static ExcelReaderSheetBuilder readSheet(String sheetName) { @@ -388,10 +339,8 @@ public class EasyExcelFactory { /** * Build excel the 'readSheet' * - * @param sheetNo - * Index of sheet,0 base. - * @param sheetName - * The name of sheet. + * @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) { diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java index 09188cb2..dcf7f548 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java @@ -5,6 +5,7 @@ import java.util.Map; import com.alibaba.excel.analysis.v03.IgnorableXlsRecordHandler; import com.alibaba.excel.constant.BuiltinFormats; +import com.alibaba.excel.constant.EasyExcelConstants; import com.alibaba.excel.context.xls.XlsReadContext; import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.RowTypeEnum; @@ -56,7 +57,9 @@ public class FormulaRecordHandler extends AbstractXlsRecordHandler implements Ig break; case NUMERIC: tempCellData.setType(CellDataTypeEnum.NUMBER); - tempCellData.setNumberValue(BigDecimal.valueOf(frec.getValue())); + 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(); diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java index 1573b8e7..031b895f 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java @@ -4,6 +4,7 @@ import java.math.BigDecimal; import com.alibaba.excel.analysis.v03.IgnorableXlsRecordHandler; import com.alibaba.excel.constant.BuiltinFormats; +import com.alibaba.excel.constant.EasyExcelConstants; import com.alibaba.excel.context.xls.XlsReadContext; import com.alibaba.excel.enums.RowTypeEnum; import com.alibaba.excel.metadata.data.DataFormatData; @@ -22,7 +23,7 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler implements Ign @Override public void processRecord(XlsReadContext xlsReadContext, Record record) { NumberRecord nr = (NumberRecord)record; - ReadCellData cellData = ReadCellData.newInstance(BigDecimal.valueOf(nr.getValue()), nr.getRow(), + ReadCellData cellData = ReadCellData.newInstanceOriginal(BigDecimal.valueOf(nr.getValue()), nr.getRow(), (int)nr.getColumn()); short dataFormat = (short)xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatIndex( nr); diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java index 38918fb8..5104b1e3 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java @@ -8,6 +8,7 @@ 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; @@ -25,17 +26,23 @@ import com.alibaba.excel.metadata.CellExtra; import com.alibaba.excel.read.metadata.ReadSheet; import com.alibaba.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder; import com.alibaba.excel.util.FileUtils; +import com.alibaba.excel.util.MapUtils; import com.alibaba.excel.util.SheetUtils; import com.alibaba.excel.util.StringUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +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.CommentsTable; +import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.usermodel.XSSFComment; import org.apache.poi.xssf.usermodel.XSSFRelation; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook; @@ -52,6 +59,20 @@ import org.xml.sax.XMLReader; @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; @@ -68,11 +89,9 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { OPCPackage pkg = readOpcPackage(xlsxReadWorkbookHolder, decryptedStream); xlsxReadWorkbookHolder.setOpcPackage(pkg); - ArrayList packageParts = pkg.getPartsByContentType(XSSFRelation.SHARED_STRINGS.getContentType()); - - if (!CollectionUtils.isEmpty(packageParts)) { - PackagePart sharedStringsTablePackagePart = packageParts.get(0); - + // Read the Shared information Strings + PackagePart sharedStringsTablePackagePart = pkg.getPart(SHARED_STRINGS_PART_NAME); + if (sharedStringsTablePackagePart != null) { // Specify default cache defaultReadCache(xlsxReadWorkbookHolder, sharedStringsTablePackagePart); @@ -89,6 +108,9 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { 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()) { @@ -104,6 +126,20 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { commentsTableMap.put(index, commentsTable); } } + 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++; } } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java index ce84d5e2..75e86aab 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java @@ -2,6 +2,7 @@ package com.alibaba.excel.analysis.v07.handlers; import java.math.BigDecimal; +import com.alibaba.excel.constant.EasyExcelConstants; import com.alibaba.excel.constant.ExcelXmlConstants; import com.alibaba.excel.context.xlsx.XlsxReadContext; import com.alibaba.excel.enums.CellDataTypeEnum; @@ -88,7 +89,9 @@ public class CellTagHandler extends AbstractXlsxTagHandler { break; } tempCellData.setType(CellDataTypeEnum.NUMBER); - tempCellData.setNumberValue(BigDecimal.valueOf(Double.parseDouble(tempDataString))); + tempCellData.setOriginalNumberValue(new BigDecimal(tempDataString)); + tempCellData.setNumberValue( + tempCellData.getOriginalNumberValue().round(EasyExcelConstants.EXCEL_MATH_CONTEXT)); break; default: throw new IllegalStateException("Cannot set values now"); diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java index ee0154e7..3f9e2ce7 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java @@ -1,5 +1,9 @@ package com.alibaba.excel.analysis.v07.handlers; +import java.util.Optional; + +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; import org.xml.sax.Attributes; import com.alibaba.excel.constant.ExcelXmlConstants; @@ -23,13 +27,32 @@ public class HyperlinkTagHandler extends AbstractXlsxTagHandler { @Override public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) { String ref = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_REF); - String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION); if (StringUtils.isEmpty(ref)) { return; } - CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, location, ref); - xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); - xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); + // 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-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java index f4514e43..203db6c7 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java @@ -4,6 +4,7 @@ import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; import com.alibaba.excel.cache.ReadCache; +import com.alibaba.excel.constant.ExcelXmlConstants; /** * Sax read sharedStringsTable.xml @@ -11,12 +12,6 @@ import com.alibaba.excel.cache.ReadCache; * @author Jiaju Zhuang */ public class SharedStringsTableHandler extends DefaultHandler { - private static final String T_TAG = "t"; - private static final String SI_TAG = "si"; - /** - * Mac 2016 2017 will have this extra field to ignore - */ - private static final String RPH_TAG = "rPh"; /** * The final piece of data @@ -43,34 +38,64 @@ public class SharedStringsTableHandler extends DefaultHandler { @Override public void startElement(String uri, String localName, String name, Attributes attributes) { - if (T_TAG.equals(name)) { - currentElementData = null; - isTagt = true; - } else if (SI_TAG.equals(name)) { - currentData = null; - } else if (RPH_TAG.equals(name)) { - ignoreTagt = true; + 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 (T_TAG.equals(name)) { - if (currentElementData != null) { + 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) { - currentData = new StringBuilder(); + readCache.put(null); + } else { + readCache.put(currentData.toString()); } - currentData.append(currentElementData); - } - isTagt = false; - } else if (SI_TAG.equals(name)) { - if (currentData == null) { - readCache.put(null); - } else { - readCache.put(currentData.toString()); - } - } else if (RPH_TAG.equals(name)) { - ignoreTagt = false; + break; + case ExcelXmlConstants.SHAREDSTRINGS_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_X_RPH_TAG: + case ExcelXmlConstants.SHAREDSTRINGS_NS2_RPH_TAG: + ignoreTagt = false; + break; + default: + // ignore } } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java index 61805cd5..7b48c8a6 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java @@ -26,33 +26,41 @@ import org.xml.sax.helpers.DefaultHandler; @Slf4j public class XlsxRowHandler extends DefaultHandler { private final XlsxReadContext xlsxReadContext; - private static final Map XLSX_CELL_HANDLER_MAP = new HashMap(32); + 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) { diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/constant/EasyExcelConstants.java b/easyexcel-core/src/main/java/com/alibaba/excel/constant/EasyExcelConstants.java new file mode 100644 index 00000000..cdc414fa --- /dev/null +++ b/easyexcel-core/src/main/java/com/alibaba/excel/constant/EasyExcelConstants.java @@ -0,0 +1,19 @@ +package com.alibaba.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-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java b/easyexcel-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java index 38c20050..c1d33592 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java @@ -17,16 +17,28 @@ public class ExcelXmlConstants { 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 @@ -49,9 +61,39 @@ public class ExcelXmlConstants { */ 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-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java b/easyexcel-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java index 34bb7536..02ba26aa 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java @@ -9,6 +9,7 @@ import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.DateUtils; import org.apache.poi.ss.usermodel.DateUtil; @@ -33,11 +34,11 @@ public class DateNumberConverter implements Converter { public Date convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), - globalConfiguration.getUse1904windowing(), null); + return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); } else { - return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), - contentProperty.getDateTimeFormatProperty().getUse1904windowing(), null); + return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()); } } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java b/easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java index f57f0bdc..4542fd08 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java @@ -9,6 +9,7 @@ import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.DateUtils; import org.apache.poi.ss.usermodel.DateUtil; @@ -33,10 +34,10 @@ public class LocalDateNumberConverter implements Converter { public LocalDateTime convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(), + return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), globalConfiguration.getUse1904windowing()); } else { - return DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(), + return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), contentProperty.getDateTimeFormatProperty().getUse1904windowing()); } } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java b/easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java index 0d0813d0..ff96317d 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java @@ -37,9 +37,8 @@ public class StringNumberConverter implements Converter { GlobalConfiguration globalConfiguration) { // If there are "DateTimeFormat", read as date if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { - return DateUtils.format( - DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), - contentProperty.getDateTimeFormatProperty().getUse1904windowing(), null), + return DateUtils.format(cellData.getNumberValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing(), contentProperty.getDateTimeFormatProperty().getFormat()); } // If there are "NumberFormat", read as number diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/enums/ReadDefaultReturnEnum.java b/easyexcel-core/src/main/java/com/alibaba/excel/enums/ReadDefaultReturnEnum.java new file mode 100644 index 00000000..4c2a317f --- /dev/null +++ b/easyexcel-core/src/main/java/com/alibaba/excel/enums/ReadDefaultReturnEnum.java @@ -0,0 +1,40 @@ +package com.alibaba.excel.enums; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.excel.metadata.data.CellData; +import com.alibaba.excel.util.StringUtils; + +/** + * 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 com.alibaba.excel.metadata.data.ReadCellData}, can decide which field you need. + */ + READ_CELL_DATA, + ; + +} diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java index 2f74ea9b..e70228f7 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java @@ -1,6 +1,7 @@ package com.alibaba.excel.metadata.data; import java.math.BigDecimal; +import java.time.LocalDateTime; import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.AbstractCell; @@ -57,6 +58,7 @@ public class CellData extends AbstractCell { } switch (type) { case STRING: + case DIRECT_STRING: case ERROR: if (StringUtils.isEmpty(stringValue)) { type = CellDataTypeEnum.EMPTY; @@ -76,5 +78,4 @@ public class CellData extends AbstractCell { } } - } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java index e1bb57ca..1d3a2a2f 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java @@ -1,7 +1,9 @@ package com.alibaba.excel.metadata.data; import java.math.BigDecimal; +import java.time.LocalDateTime; +import com.alibaba.excel.constant.EasyExcelConstants; import com.alibaba.excel.enums.CellDataTypeEnum; import lombok.EqualsAndHashCode; @@ -11,6 +13,7 @@ import lombok.Setter; /** * read cell data + *

* * @author Jiaju Zhuang */ @@ -19,6 +22,29 @@ import lombok.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. */ @@ -107,11 +133,21 @@ public class ReadCellData extends CellData { 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()); @@ -123,5 +159,4 @@ public class ReadCellData extends CellData { } return readCellData; } - } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java index ba8fee47..1bd591e3 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java @@ -212,10 +212,10 @@ public class DataFormatter { CellFormat cfmt = CellFormat.getInstance(locale, formatStr); // CellFormat requires callers to identify date vs not, so do so Object cellValueO = data; - if (DateUtil.isADateFormat(dataFormat, formatStr) && + 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 = DateUtil.getJavaDate(data, use1904windowing); + cellValueO = DateUtils.getJavaDate(data, use1904windowing); } // Wrap and return (non-cachable - CellFormat does that) return new CellFormatResultWrapper(cfmt.apply(cellValueO)); @@ -243,6 +243,8 @@ public class DataFormatter { return format; } + + private Format createFormat(Short dataFormat, String dataFormatString) { String formatStr = dataFormatString; @@ -628,7 +630,7 @@ public class DataFormatter { // Hint about the raw excel value ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(data); } - return performDateFormatting(DateUtil.getJavaDate(data, use1904windowing), dateFormat); + return performDateFormatting(DateUtils.getJavaDate(data, use1904windowing), dateFormat); } /** diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java index d2a24b44..59fd7e1f 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java @@ -13,6 +13,7 @@ import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellExtraTypeEnum; +import com.alibaba.excel.enums.ReadDefaultReturnEnum; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.event.SyncReadListener; import com.alibaba.excel.read.listener.ModelBuildEventListener; @@ -198,6 +199,17 @@ public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder>> { - @Override - public void invokeHead(Map> cellDataMap, AnalysisContext context) { - if (context.readSheetHolder().getMaxDataHeadSize() == null - || context.readSheetHolder().getMaxDataHeadSize() < CollectionUtils.size(cellDataMap)) { - context.readSheetHolder().setMaxDataHeadSize(CollectionUtils.size(cellDataMap)); - } - } - @Override public void invoke(Map> cellDataMap, AnalysisContext context) { ReadSheetHolder readSheetHolder = context.readSheetHolder(); @@ -41,13 +42,13 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener> cellDataMap, ReadSheetHolder readSheetHolder, + private Object buildNoModel(Map> cellDataMap, ReadSheetHolder readSheetHolder, AnalysisContext context) { int index = 0; - Map map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size()); + Map map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size()); for (Map.Entry> entry : cellDataMap.entrySet()) { Integer key = entry.getKey(); ReadCellData cellData = entry.getValue(); @@ -56,9 +57,23 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener 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); @@ -69,12 +84,44 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener 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.getMaxDataHeadSize() != null) { - return readSheetHolder.getMaxDataHeadSize(); + if (readSheetHolder.getMaxNotEmptyDataHeadSize() != null) { + return readSheetHolder.getMaxNotEmptyDataHeadSize(); } return 0; } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java index 4f3a3aab..a3713d7c 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java @@ -11,6 +11,7 @@ import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellExtraTypeEnum; +import com.alibaba.excel.enums.ReadDefaultReturnEnum; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.read.listener.ModelBuildEventListener; import com.alibaba.excel.support.ExcelTypeEnum; @@ -62,7 +63,6 @@ public class ReadWorkbook extends ReadBasicParameter { /** * This object can be read in the Listener {@link AnalysisEventListener#invoke(Object, AnalysisContext)} * {@link AnalysisContext#getCustom()} - * */ private Object customObject; /** @@ -96,8 +96,18 @@ public class ReadWorkbook extends ReadBasicParameter { * 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. * diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java index 197835ad..47957044 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java @@ -65,7 +65,7 @@ public class ReadSheetHolder extends AbstractReadHolder { * Read the size of the largest head in sheet head data. * see https://github.com/alibaba/easyexcel/issues/2014 */ - private Integer maxDataHeadSize; + private Integer maxNotEmptyDataHeadSize; public ReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { super(readSheet, readWorkbookHolder); diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java index a84cc315..63f8c1bf 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java @@ -14,6 +14,7 @@ import com.alibaba.excel.cache.selector.SimpleReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellExtraTypeEnum; import com.alibaba.excel.enums.HolderEnum; +import com.alibaba.excel.enums.ReadDefaultReturnEnum; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.read.metadata.ReadSheet; @@ -64,10 +65,20 @@ public class ReadWorkbookHolder extends AbstractReadHolder { * 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 */ @@ -146,6 +157,12 @@ public class ReadWorkbookHolder extends AbstractReadHolder { 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; diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java index f4ed56e7..9fa9182d 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java @@ -10,6 +10,7 @@ import com.alibaba.excel.read.metadata.holder.ReadWorkbookHolder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; /** * sheet holder @@ -36,9 +37,16 @@ public class XlsxReadSheetHolder extends ReadSheetHolder { * 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-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java index 4e70aa0e..fbeaf048 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java @@ -15,6 +15,7 @@ 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; @@ -51,6 +52,11 @@ public class XlsxReadWorkbookHolder extends ReadWorkbookHolder { */ private Map dataFormatDataCache; + /** + * excel Relationship, key: sheetNo value: PackageRelationshipCollection + */ + private Map packageRelationshipCollectionMap; + public XlsxReadWorkbookHolder(ReadWorkbook readWorkbook) { super(readWorkbook); this.saxParserFactoryName = readWorkbook.getXlsxSAXParserFactoryName(); @@ -65,6 +71,9 @@ public class XlsxReadWorkbookHolder extends ReadWorkbookHolder { 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())); diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java b/easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java index 45edb7b7..642e71fb 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java @@ -3,8 +3,11 @@ package com.alibaba.excel.read.processor; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.HeadKindEnum; import com.alibaba.excel.enums.RowTypeEnum; import com.alibaba.excel.exception.ExcelAnalysisException; @@ -17,6 +20,8 @@ import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty; import com.alibaba.excel.util.ConverterUtils; import com.alibaba.excel.util.StringUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,6 +116,14 @@ public class DefaultAnalysisEventProcessor implements AnalysisEventProcessor { } 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; } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java b/easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java index 02ea4a00..6359a99d 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -84,24 +84,52 @@ public class ConverterUtils { public static Object convertToJavaObject(ReadCellData cellData, Field field, ExcelContentProperty contentProperty, Map> converterMap, AnalysisContext context, Integer rowIndex, Integer columnIndex) { - Class clazz; - if (field == null) { - clazz = String.class; - } else { - clazz = field.getType(); + 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) { - Class classGeneric = getClassGeneric(field.getGenericType()); ReadCellData cellDataReturn = cellData.clone(); - cellDataReturn.setData(doConvertToJavaObject(cellData, classGeneric, contentProperty, converterMap, - context, rowIndex, columnIndex)); + 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(Type type) { + 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; } diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java b/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java index 22d7f3d9..fb19a105 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,9 +1,11 @@ package com.alibaba.excel.util; +import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashMap; @@ -11,6 +13,8 @@ import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; +import org.apache.poi.ss.usermodel.DateUtil; + /** * Date utils * @@ -184,6 +188,33 @@ public class DateUtils { } } + /** + * 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) { @@ -200,6 +231,46 @@ public class DateUtils { 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) { + //To calculate the Date, in the use of `org.apache.poi.ss.usermodel.DateUtil.getJavaDate(double, boolean, + // java.util.TimeZone, boolean), Date when similar `2023-01-01 00:00:00.500`, returns the`2023-01-01 + // 00:00:01`, but excel in fact shows the `2023-01-01 00:00:00`. + // `org.apache.poi.ss.usermodel.DateUtil.getLocalDateTime(double, boolean, boolean)` There is no problem. + return Date.from(getLocalDateTime(date, use1904windowing).atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * 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); + } + /** * Determine if it is a date format. * diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java b/easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java index 41a4be0e..881c1437 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java @@ -62,7 +62,6 @@ public class FileUtils { // Initialize the cache directory File cacheFile = new File(cachePath); createDirectory(cacheFile); - cacheFile.deleteOnExit(); } /** @@ -160,7 +159,6 @@ public class FileUtils { File poiFilesPathFile = new File(poiFilesPath); createDirectory(poiFilesPathFile); TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiFilesPathFile)); - poiFilesPathFile.deleteOnExit(); } public static File createCacheTmpFile() { diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java b/easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java index cb86a3e0..b1fe86a3 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java @@ -18,17 +18,17 @@ public interface RowData { /** * Returns the number of elements in this collection. If this collection - * contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. + * 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. + * Returns true if this collection contains no elements. * - * @return true if this collection contains no elements + * @return true if this collection contains no elements */ boolean isEmpty(); diff --git a/easyexcel-support/pom.xml b/easyexcel-support/pom.xml index d15b0d7a..db2dbd3f 100644 --- a/easyexcel-support/pom.xml +++ b/easyexcel-support/pom.xml @@ -13,6 +13,8 @@ jar easyexcel-support + easyexcel-support + diff --git a/easyexcel-test/pom.xml b/easyexcel-test/pom.xml index 65a25e8b..5c893b45 100644 --- a/easyexcel-test/pom.xml +++ b/easyexcel-test/pom.xml @@ -13,6 +13,7 @@ jar easyexcel-test + easyexcel-test true diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java index 027aa54f..f4aae4a8 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java @@ -1,10 +1,14 @@ package com.alibaba.easyexcel.test.core.compatibility; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.constant.EasyExcelConstants; +import com.alibaba.excel.enums.ReadDefaultReturnEnum; +import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; @@ -30,4 +34,86 @@ public class CompatibilityTest { Map row1 = list.get(1); Assert.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)); + Assert.assertEquals(3, list.size()); + Map row2 = list.get(2); + Assert.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)); + Assert.assertEquals(1, list.size()); + Map row0 = list.get(0); + Assert.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)); + Assert.assertEquals(56, list.size()); + Map row0 = list.get(0); + Assert.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)); + Assert.assertEquals("2023-01-01 00:00:00", list.get(0).get(0)); + Assert.assertEquals("2023-01-01 00:00:00", list.get(1).get(0)); + Assert.assertEquals("2023-01-01 00:00:00", list.get(2).get(0)); + Assert.assertEquals("2023-01-01 00:00:01", list.get(3).get(0)); + Assert.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)); + Assert.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)); + Assert.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)); + Assert.assertEquals("24.20", list.get(0).get(11)); + } + } diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java index cf7418d5..4475d83c 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.core.extra; import java.io.File; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -9,10 +10,13 @@ import org.slf4j.LoggerFactory; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellExtraTypeEnum; +import com.alibaba.excel.metadata.CellExtra; +import com.alibaba.excel.read.listener.ReadListener; +import com.alibaba.fastjson.JSON; /** - * * @author Jiaju Zhuang */ public class ExtraDataTest { @@ -20,10 +24,13 @@ public class ExtraDataTest { private static File file03; private static File file07; + private static File extraRelationships; + @BeforeClass 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 @@ -36,6 +43,41 @@ public class ExtraDataTest { 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())) { + Assert.assertEquals(1, (int)extra.getRowIndex()); + Assert.assertEquals(0, (int)extra.getColumnIndex()); + } else if ("333333333333".equals(extra.getText())) { + Assert.assertEquals(1, (int)extra.getRowIndex()); + Assert.assertEquals(1, (int)extra.getColumnIndex()); + } else { + Assert.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-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java index 262ae35d..5abbcba7 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java @@ -1,14 +1,20 @@ package com.alibaba.easyexcel.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 com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.enums.ReadDefaultReturnEnum; +import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.util.DateUtils; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.FixMethodOrder; @@ -19,6 +25,7 @@ import org.junit.runners.MethodSorters; * @author Jiaju Zhuang */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Slf4j public class NoModelDataTest { private static File file07; @@ -28,7 +35,6 @@ public class NoModelDataTest { private static File fileRepeat03; private static File fileRepeatCsv; - @BeforeClass public static void init() { file07 = TestFileUtil.createNewFile("noModel07.xlsx"); @@ -41,20 +47,20 @@ public class NoModelDataTest { @Test public void t01ReadAndWrite07() throws Exception { - readAndWrite(file07, fileRepeat07); + readAndWrite(file07, fileRepeat07, false); } @Test public void t02ReadAndWrite03() throws Exception { - readAndWrite(file03, fileRepeat03); + readAndWrite(file03, fileRepeat03, false); } @Test public void t03ReadAndWriteCsv() throws Exception { - readAndWrite(fileCsv, fileRepeatCsv); + readAndWrite(fileCsv, fileRepeatCsv, true); } - private void readAndWrite(File file, File fileRepeat) throws Exception { + 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(); Assert.assertEquals(10, result.size()); @@ -63,6 +69,42 @@ public class NoModelDataTest { Assert.assertEquals("109", data10.get(1)); Assert.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)); + Assert.assertEquals(10, actualDataList.size()); + Map actualData10 = actualDataList.get(9); + Assert.assertEquals("string19", actualData10.get(0)); + if (isCsv) { + // CSV only string type + Assert.assertEquals("109", actualData10.get(1)); + Assert.assertEquals("2020-01-01 01:01:01", actualData10.get(2)); + } else { + Assert.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)actualData10.get(1))); + Assert.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)); + Assert.assertEquals(10, readCellDataList.size()); + Map> readCellData10 = readCellDataList.get(9); + Assert.assertEquals("string19", readCellData10.get(0).getData()); + if (isCsv) { + // CSV only string type + Assert.assertEquals("109", readCellData10.get(1).getData()); + Assert.assertEquals("2020-01-01 01:01:01", readCellData10.get(2).getData()); + } else { + Assert.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)readCellData10.get(1).getData())); + Assert.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(); Assert.assertEquals(10, result.size()); diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java index 0c71753e..618a030f 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java @@ -83,28 +83,13 @@ public class FillTest { return data(); }); - // 方案3.1 分多次 填充 会使用文件缓存(省内存) 使用 try-with-resources @since 3.1.0 + // 方案3 分多次 填充 会使用文件缓存(省内存) 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); } - - // 方案3.2 分多次 填充 会使用文件缓存(省内存) 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; - ExcelWriter excelWriter = null; - try { - excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); - WriteSheet writeSheet = EasyExcel.writerSheet().build(); - excelWriter.fill(data(), writeSheet); - excelWriter.fill(data(), writeSheet); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -120,7 +105,7 @@ public class FillTest { TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx"; String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; - // 方案1 : 使用 try-with-resources @since 3.1.0 + // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 @@ -135,29 +120,6 @@ public class FillTest { map.put("total", 1000); excelWriter.fill(map, writeSheet); } - - // 方案2 : 不使用 try-with-resources - ExcelWriter excelWriter = null; - try { - 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); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -177,7 +139,7 @@ public class FillTest { String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; - // 方案1 : 使用 try-with-resources @since 3.1.0 + // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 直接写入数据 @@ -204,42 +166,6 @@ public class FillTest { // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 } - - // 方案2 : 不使用 try-with-resources - ExcelWriter excelWriter = null; - try { - 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需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -255,7 +181,7 @@ public class FillTest { TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx"; String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx"; - // 方案1 : 使用 try-with-resources @since 3.1.0 + // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); @@ -266,25 +192,6 @@ public class FillTest { map.put("date", "2019年10月9日13:28:28"); excelWriter.fill(map, writeSheet); } - - // 方案2 : 不使用 try-with-resources - ExcelWriter excelWriter = null; - try { - 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); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -301,7 +208,7 @@ public class FillTest { String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx"; - // 方案1 : 使用 try-with-resources @since 3.1.0 + // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); @@ -319,32 +226,6 @@ public class FillTest { excelWriter.fill(map, writeSheet); } - - // 方案2 : 不使用 try-with-resources - ExcelWriter excelWriter = null; - try { - 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); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } private List data() { diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/rare/WriteTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/rare/WriteTest.java new file mode 100644 index 00000000..c35d7f96 --- /dev/null +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/rare/WriteTest.java @@ -0,0 +1,143 @@ +package com.alibaba.easyexcel.test.demo.rare; + +import java.io.File; +import java.util.Date; +import java.util.List; + +import com.alibaba.easyexcel.test.demo.write.DemoData; +import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.util.FileUtils; +import com.alibaba.excel.util.ListUtils; +import com.alibaba.excel.write.handler.RowWriteHandler; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.handler.WorkbookWriteHandler; +import com.alibaba.excel.write.handler.context.RowWriteHandlerContext; +import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext; +import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext; +import com.alibaba.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.Ignore; +import org.junit.Test; + +/** + * 记录一些不太常见的案例 + * + * @author Jiaju Zhuang + */ +@Ignore +@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-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 86f57f2b..c613ee15 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -47,7 +47,7 @@ public class ReadTest { // since: 3.0.0-beta1 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 - // 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行 + // 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行 EasyExcel.read(fileName, DemoData.class, new PageReadListener(dataList -> { for (DemoData demoData : dataList) { log.info("读取到一条数据{}", JSON.toJSONString(demoData)); @@ -98,7 +98,7 @@ public class ReadTest { // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); - // 写法4: 使用 try-with-resources @since 3.1.0 + // 写法4 fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 一个文件一个reader try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) { @@ -107,23 +107,6 @@ public class ReadTest { // 读取一个sheet excelReader.read(readSheet); } - - // 写法5: 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; - // 一个文件一个reader - ExcelReader excelReader = null; - try { - excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); - // 构建一个sheet 这里可以指定名字或者no - ReadSheet readSheet = EasyExcel.readSheet(0).build(); - // 读取一个sheet - excelReader.read(readSheet); - } finally { - if (excelReader != null) { - // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 - excelReader.close(); - } - } } /** @@ -162,7 +145,7 @@ public class ReadTest { // 读取部分sheet fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; - // 写法1: 使用 try-with-resources @since 3.1.0 + // 写法1 try (ExcelReader excelReader = EasyExcel.read(fileName).build()) { // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener ReadSheet readSheet1 = @@ -172,25 +155,6 @@ public class ReadTest { // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能 excelReader.read(readSheet1, readSheet2); } - - // 写法2: 不使用 try-with-resources - ExcelReader excelReader = null; - try { - 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); - } finally { - if (excelReader != null) { - // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 - excelReader.close(); - } - } } /** diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java index 76b50584..fc3079a1 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java @@ -24,6 +24,7 @@ public class DemoData { private Date date; @ExcelProperty("数字标题") private Double doubleData; + /** * 忽略这个字段 */ diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index f7d015ca..c3c15a3f 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -86,28 +86,13 @@ public class WriteTest { // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); - // 写法3:使用 try-with-resources @since 3.1.0 + // 写法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); } - - // 写法4: 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去写 - ExcelWriter excelWriter = null; - try { - excelWriter = EasyExcel.write(fileName, DemoData.class).build(); - WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); - excelWriter.write(data(), writeSheet); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -185,7 +170,7 @@ public class WriteTest { */ @Test public void repeatedWrite() { - // 方法1.1: 如果写到同一个sheet 使用 try-with-resources @since 3.1.0 + // 方法1: 如果写到同一个sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { @@ -199,28 +184,7 @@ public class WriteTest { } } - // 方法1.2: 如果写到同一个sheet 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; - ExcelWriter writer = null; - try { - // 这里 需要指定写用哪个class去写 - writer = EasyExcel.write(fileName, DemoData.class).build(); - // 这里注意 如果同一个sheet只要创建一次 - WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); - // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来 - for (int i = 0; i < 5; i++) { - // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 - List data = data(); - writer.write(data, writeSheet); - } - } finally { - // 千万别忘记close 会帮忙关闭流 - if (writer != null) { - writer.close(); - } - } - - // 方法2.1: 如果写到不同的sheet 同一个对象 使用 try-with-resources @since 3.1.0 + // 方法2: 如果写到不同的sheet 同一个对象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 指定文件 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { @@ -234,27 +198,7 @@ public class WriteTest { } } - // 方法2.2: 如果写到不同的sheet 同一个对象 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; - try { - // 这里 指定文件 - writer = 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(); - writer.write(data, writeSheet); - } - } finally { - // 千万别忘记close 会帮忙关闭流 - if (writer != null) { - writer.close(); - } - } - - // 方法3.1 如果写到不同的sheet 不同的对象 使用 try-with-resources @since 3.1.0 + // 方法3 如果写到不同的sheet 不同的对象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 指定文件 try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) { @@ -269,26 +213,6 @@ public class WriteTest { } } - // 方法3.2 如果写到不同的sheet 不同的对象 不使用 try-with-resources - fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; - try { - // 这里 指定文件 - writer = 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(); - writer.write(data, writeSheet); - } - } finally { - // 千万别忘记close 会帮忙关闭流 - if (writer != null) { - writer.close(); - } - } } /** @@ -649,7 +573,7 @@ public class WriteTest { @Test public void tableWrite() { String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; - // 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案, 使用 try-with-resources @since 3.1.0 + // 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案 // 这里 需要指定写用哪个class去写 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 @@ -662,27 +586,6 @@ public class WriteTest { // 第二次写如也会创建头,然后在第一次的后面写入数据 excelWriter.write(data(), writeSheet, writeTable1); } - - // 方法2 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案, 不使用 try-with-resources - // 这里 需要指定写用哪个class去写 - ExcelWriter excelWriter = null; - try { - 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); - } finally { - // 千万别忘记close 会帮忙关闭流 - if (excelWriter != null) { - excelWriter.close(); - } - } } /** @@ -839,8 +742,8 @@ public class WriteTest { for (int i = 0; i < 10; i++) { List data = ListUtils.newArrayList(); data.add("字符串" + i); - data.add(new Date()); data.add(0.56); + data.add(new Date()); list.add(data); } return list; diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData2.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData2.java new file mode 100644 index 00000000..98693438 --- /dev/null +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData2.java @@ -0,0 +1,35 @@ +package com.alibaba.easyexcel.test.temp; + +import java.math.BigDecimal; +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.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-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData3.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData3.java new file mode 100644 index 00000000..0be8ec7a --- /dev/null +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData3.java @@ -0,0 +1,24 @@ +package com.alibaba.easyexcel.test.temp; + +import java.time.LocalDateTime; +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.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-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java index 34e25d0d..283ce133 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java @@ -1,32 +1,49 @@ package com.alibaba.easyexcel.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 com.alibaba.easyexcel.test.demo.write.DemoData; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.util.NumberDataFormatterUtils; +import com.alibaba.excel.util.NumberUtils; import com.alibaba.excel.util.PositionUtils; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.hssf.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.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; /** * 临时测试 * * @author Jiaju Zhuang **/ +@Slf4j public class Lock2Test { private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class); @@ -37,7 +54,11 @@ public class Lock2Test { // 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/测试格式.xlsx").sheet(0).headRowNumber(0).doReadSync(); + List list = EasyExcel.read( + "/Users/zhuangjiaju/IdeaProjects/easyexcel/easyexcel-test/target/test-classes/simpleWrite1674051907397.xlsx") + //.useDefaultListener(false) + .sheet(0) + .headRowNumber(0).doReadSync(); LOGGER.info("数据:{}", list.size()); for (Object data : list) { LOGGER.info("返回数据:{}", CollectionUtils.size(data)); @@ -109,18 +130,6 @@ public class Lock2Test { return list; } - 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 testc() throws Exception { LOGGER.info("reslut:{}", JSON.toJSONString(new CellReference("B3"))); @@ -151,10 +160,9 @@ public class Lock2Test { @Test public void test335() throws Exception { - - LOGGER.info("reslut:{}", PositionUtils.getCol("A10",null)); + LOGGER.info("reslut:{}", PositionUtils.getCol("A10", null)); LOGGER.info("reslut:{}", PositionUtils.getRow("A10")); - LOGGER.info("reslut:{}", PositionUtils.getCol("AB10",null)); + LOGGER.info("reslut:{}", PositionUtils.getCol("AB10", null)); LOGGER.info("reslut:{}", PositionUtils.getRow("AB10")); //LOGGER.info("reslut:{}", PositionUtils2.getCol("A10",null)); @@ -163,5 +171,279 @@ public class Lock2Test { //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); + + Assert.assertEquals("测试基本转换错误" + dateTime, format.format(date), + format.format(DateUtil.getJavaDate(excelDate, false))); + Assert.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-test/src/test/resources/compatibility/t02.xlsx b/easyexcel-test/src/test/resources/compatibility/t02.xlsx new file mode 100644 index 00000000..b8d755de Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t02.xlsx differ diff --git a/easyexcel-test/src/test/resources/compatibility/t03.xlsx b/easyexcel-test/src/test/resources/compatibility/t03.xlsx new file mode 100644 index 00000000..3a31ef78 Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t03.xlsx differ diff --git a/easyexcel-test/src/test/resources/compatibility/t04.xlsx b/easyexcel-test/src/test/resources/compatibility/t04.xlsx new file mode 100644 index 00000000..7c95d425 Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t04.xlsx differ diff --git a/easyexcel-test/src/test/resources/compatibility/t05.xlsx b/easyexcel-test/src/test/resources/compatibility/t05.xlsx new file mode 100644 index 00000000..248ec7d1 Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t05.xlsx differ diff --git a/easyexcel-test/src/test/resources/compatibility/t06.xlsx b/easyexcel-test/src/test/resources/compatibility/t06.xlsx new file mode 100644 index 00000000..b27be027 Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t06.xlsx differ diff --git a/easyexcel-test/src/test/resources/compatibility/t07.xlsx b/easyexcel-test/src/test/resources/compatibility/t07.xlsx new file mode 100644 index 00000000..a7b0eac7 Binary files /dev/null and b/easyexcel-test/src/test/resources/compatibility/t07.xlsx differ diff --git a/easyexcel-test/src/test/resources/extra/extraRelationships.xlsx b/easyexcel-test/src/test/resources/extra/extraRelationships.xlsx new file mode 100644 index 00000000..5784cd8b Binary files /dev/null and b/easyexcel-test/src/test/resources/extra/extraRelationships.xlsx differ diff --git a/easyexcel/pom.xml b/easyexcel/pom.xml index 303f79ad..495552c0 100644 --- a/easyexcel/pom.xml +++ b/easyexcel/pom.xml @@ -13,6 +13,7 @@ jar easyexcel + easyexcel diff --git a/pom.xml b/pom.xml index b8be96c7..aabbaaaf 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ - 3.1.0 + 3.2.0 UTF-8 1.8 true @@ -229,6 +229,11 @@ flatten-maven-plugin 1.2.7 + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + @@ -307,6 +312,13 @@ + + + + --pinentry-mode + loopback + + org.apache.maven.plugins @@ -389,6 +401,18 @@ + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://oss.sonatype.org/ + true + + diff --git a/update.md b/update.md index f2bb7a64..36c98000 100644 --- a/update.md +++ b/update.md @@ -1,4 +1,31 @@ +# 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) @@ -14,24 +41,27 @@ * 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 @@ -39,7 +69,6 @@ * 修复样式可能超过最大限制的bug * 修复写入过慢的bug - # 3.0.0-beta3 * 修复导出浮点型数据可能精度异常的bug @@ -122,7 +151,8 @@ * 修复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) +* 修复`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 @@ -139,11 +169,16 @@ * 发布正式版 * 修复第一行为空不会调用`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) +* + +当类的属性没有按照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) +* 修复`table`、`sheet`中创建的拦截器不执行`workbook` + 事件的bug [Issue #1202](https://github.com/alibaba/easyexcel/issues/1202) # 2.2.0-beta2